From 59b8d6a614a204ec38735fa43d2aa076530b130b Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 14:03:36 -0800 Subject: [PATCH 01/51] Squashes all zon work into one commit for easier rebase * Gets all tests passing after rebase * Resolves a bunch of TODOs * WIP commit while I build release version of main * Errors on embedded nulls in identifiers * Parsing identifiers properly without allocating * Properly frees parsed idents * Fixes integer negation logic * Uses existing big int parser * Cleans up runtime int parsing * Some investigation into float issue * More float investigation * Adds missing test * Resolves TODOs * Fixes some error tokens * Cleans up remaining parsing todos * Removes TODOs * Excludes tests format formatting * Formats * Parses directly as desired float type * Moves zon lowering into own file Records result type for imports, does not yet use Fixes print zir for imports, better encoding of instruction Uses the result type to add indirection where necessary Does not yet remove & from syntax Removes & from comptime ZON syntax Yet to be removed from runtime syntax Fixes after rebase Allow coercion to slices, not to any other pointer type, adds tests No longer allows `&` in the runtime parser Also fixes merge Fixes bug where you could parse structs as tuples and vice versa Also cleans up error handling Reworks runtime error handling Explains usage of unreachable Fixes CI failure, and exposes notes in API Shows notes on integer literal errors when parsing ZON Fixes free of undefined value if struct with allocated fields is missing a field Skips tests failing due to C backend issue Notes why tests are skipped, renames to make easier to use test filter --- build.zig | 2 +- lib/std/fmt.zig | 3 +- lib/std/std.zig | 1 + lib/std/zig/Ast.zig | 3 +- lib/std/zig/AstGen.zig | 103 +- lib/std/zig/Zir.zig | 9 +- lib/std/zig/number_literal.zig | 75 + lib/std/zig/string_literal.zig | 206 +- lib/std/zon.zig | 97 + lib/std/zon/parse.zig | 2845 +++++++++++++++++ lib/std/zon/stringify.zig | 1920 +++++++++++ src/Air.zig | 5 + src/Compilation.zig | 8 +- src/Package/Manifest.zig | 98 +- src/Package/Module.zig | 1 + src/Sema.zig | 40 +- src/Zcu.zig | 23 +- src/Zcu/PerThread.zig | 7 +- src/main.zig | 3 + src/print_zir.zig | 12 +- src/zon.zig | 661 ++++ test/behavior/zon.zig | 268 ++ test/behavior/zon/a.zon | 1 + test/behavior/zon/a_neg.zon | 1 + test/behavior/zon/abc-escaped.zon | 1 + test/behavior/zon/abc.zon | 1 + test/behavior/zon/array.zon | 1 + test/behavior/zon/empty_struct.zon | 1 + 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 | 26 + test/behavior/zon/foo.zon | 1 + test/behavior/zon/inf_and_nan.zon | 6 + test/behavior/zon/ints.zon | 40 + test/behavior/zon/multiline_string.zon | 4 + test/behavior/zon/none.zon | 1 + test/behavior/zon/slice-1.zon | 1 + test/behavior/zon/slice-abc.zon | 1 + test/behavior/zon/slice-empty.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/vec0.zon | 1 + test/behavior/zon/vec1.zon | 1 + test/behavior/zon/vec2.zon | 1 + test/behavior/zon/void.zon | 1 + test/behavior/zon/z.zon | 1 + .../compile_errors/@import_zon_addr_slice.zig | 11 + .../compile_errors/@import_zon_array_len.zig | 13 + .../compile_errors/@import_zon_bad_import.zig | 12 + .../@import_zon_coerce_pointer.zig | 11 + .../@import_zon_double_negation_float.zig | 11 + .../@import_zon_double_negation_int.zig | 11 + .../@import_zon_enum_embedded_null.zig | 12 + .../@import_zon_invalid_character.zig | 11 + .../@import_zon_invalid_number.zig | 11 + .../@import_zon_invalid_string.zig | 11 + .../@import_zon_leading_zero_in_integer.zig | 12 + .../@import_zon_negative_zero.zig | 11 + .../@import_zon_negative_zero_cast_float.zig | 11 + .../@import_zon_number_fail_limits.zig | 11 + .../@import_zon_struct_dup_field.zig | 12 + .../@import_zon_syntax_error.zig | 11 + .../compile_errors/@import_zon_type_decl.zig | 11 + .../@import_zon_type_expr_array.zig | 11 + .../@import_zon_type_expr_fn.zig | 11 + .../@import_zon_type_expr_struct.zig | 11 + .../@import_zon_type_expr_tuple.zig | 11 + .../@import_zon_type_mismatch.zig | 11 + .../@import_zon_unescaped_newline.zig | 12 + .../@import_zon_unknown_ident.zig | 11 + test/cases/compile_errors/zon/addr_slice.zon | 3 + test/cases/compile_errors/zon/array.zon | 1 + test/cases/compile_errors/zon/desktop.ini | 3 + .../zon/double_negation_float.zon | 1 + .../zon/double_negation_int.zon | 1 + .../compile_errors/zon/enum_embedded_null.zon | 4 + .../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 + .../compile_errors/zon/negative_zero.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/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/src/Cases.zig | 44 +- 99 files changed, 6687 insertions(+), 167 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/zon.zig create mode 100644 test/behavior/zon.zig create mode 100644 test/behavior/zon/a.zon create mode 100644 test/behavior/zon/a_neg.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/empty_struct.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/slice-1.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/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/vec0.zon create mode 100644 test/behavior/zon/vec1.zon create mode 100644 test/behavior/zon/vec2.zon create mode 100644 test/behavior/zon/void.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_coerce_pointer.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_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_negative_zero.zig create mode 100644 test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig create mode 100644 test/cases/compile_errors/@import_zon_number_fail_limits.zig create mode 100644 test/cases/compile_errors/@import_zon_struct_dup_field.zig create mode 100644 test/cases/compile_errors/@import_zon_syntax_error.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/zon/addr_slice.zon create mode 100644 test/cases/compile_errors/zon/array.zon create mode 100644 test/cases/compile_errors/zon/desktop.ini 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/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/negative_zero.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/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 diff --git a/build.zig b/build.zig index d79a785c76a2..574ba62593d2 100644 --- a/build.zig +++ b/build.zig @@ -404,7 +404,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/std/fmt.zig b/lib/std/fmt.zig index 2b5e78975e97..078b40548dba 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1581,7 +1581,8 @@ test parseInt { try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16)); } -fn parseIntWithSign( +/// Like `parseIntWithGenericCharacter`, but with a sign argument. +pub fn parseIntWithSign( comptime Result: type, comptime Character: type, buf: []const Character, diff --git a/lib/std/std.zig b/lib/std/std.zig index cc61111746aa..20543b67d893 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -44,6 +44,7 @@ pub const Thread = @import("Thread.zig"); pub const Treap = @import("treap.zig").Treap; pub const Tz = tz.Tz; pub const Uri = @import("Uri.zig"); +pub const zon = @import("zon.zig"); pub const array_hash_map = @import("array_hash_map.zig"); pub const atomic = @import("atomic.zig"); diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 335168322f55..d9050e9659f1 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -7,12 +7,13 @@ /// Reference to externally-owned data. source: [:0]const u8, +mode: Mode, + tokens: TokenList.Slice, /// The root AST node is assumed to be index 0. Since there can be no /// references to the root node, this means 0 is available to indicate null. nodes: NodeList.Slice, extra_data: []Node.Index, -mode: Mode = .zig, errors: []const Error, diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index d318a9f3e505..2baeaed896c0 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8888,36 +8888,22 @@ fn numberLiteral(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index, source_node: } } -fn failWithNumberError(astgen: *AstGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) InnerError { - const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null; - switch (err) { - .leading_zero => if (is_float) { - return astgen.failTok(token, "number '{s}' has leading zero", .{bytes}); - } else { - return astgen.failTokNotes(token, "number '{s}' has leading zero", .{bytes}, &.{ - try astgen.errNoteTok(token, "use '0o' prefix for octal literals", .{}), - }); - }, - .digit_after_base => return astgen.failTok(token, "expected a digit after base prefix", .{}), - .upper_case_base => |i| return astgen.failOff(token, @intCast(i), "base prefix must be lowercase", .{}), - .invalid_float_base => |i| return astgen.failOff(token, @intCast(i), "invalid base for float literal", .{}), - .repeated_underscore => |i| return astgen.failOff(token, @intCast(i), "repeated digit separator", .{}), - .invalid_underscore_after_special => |i| return astgen.failOff(token, @intCast(i), "expected digit before digit separator", .{}), - .invalid_digit => |info| return astgen.failOff(token, @intCast(info.i), "invalid digit '{c}' for {s} base", .{ bytes[info.i], @tagName(info.base) }), - .invalid_digit_exponent => |i| return astgen.failOff(token, @intCast(i), "invalid digit '{c}' in exponent", .{bytes[i]}), - .duplicate_exponent => |i| return astgen.failOff(token, @intCast(i), "duplicate exponent", .{}), - .exponent_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before exponent", .{}), - .special_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before '{c}'", .{bytes[i]}), - .trailing_special => |i| return astgen.failOff(token, @intCast(i), "expected digit after '{c}'", .{bytes[i - 1]}), - .trailing_underscore => |i| return astgen.failOff(token, @intCast(i), "trailing digit separator", .{}), - .duplicate_period => unreachable, // Validated by tokenizer - .invalid_character => unreachable, // Validated by tokenizer - .invalid_exponent_sign => |i| { - assert(bytes.len >= 2 and bytes[0] == '0' and bytes[1] == 'x'); // Validated by tokenizer - return astgen.failOff(token, @intCast(i), "sign '{c}' cannot follow digit '{c}' in hex base", .{ bytes[i], bytes[i - 1] }); - }, - .period_after_exponent => |i| return astgen.failOff(token, @intCast(i), "unexpected period after exponent", .{}), - } +fn failWithNumberError( + astgen: *AstGen, + err: std.zig.number_literal.Error, + token: Ast.TokenIndex, + bytes: []const u8, +) InnerError { + const note = err.noteWithSource(bytes); + const notes: []const u32 = if (note) |n| &.{try astgen.errNoteTok(token, "{s}", .{n})} else &.{}; + try astgen.appendErrorTokNotesOff( + token, + @as(u32, @intCast(err.offset())), + "{}", + .{err.fmtWithSource(bytes)}, + notes, + ); + return error.AnalysisFail; } fn asmExpr( @@ -9412,7 +9398,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; @@ -11507,9 +11504,20 @@ 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 astgen.failOff( + token, + offset + @as(u32, @intCast(err.offset())), + "{}", + .{err.fmtWithSource(raw_string)}, + ); } fn failNode( @@ -11627,7 +11635,7 @@ fn appendErrorTokNotesOff( comptime format: []const u8, args: anytype, notes: []const u32, -) !void { +) Allocator.Error!void { @branchHint(.cold); const gpa = astgen.gpa; const string_bytes = &astgen.string_bytes; @@ -11756,32 +11764,17 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice { } fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice { - const tree = astgen.tree; - const node_datas = tree.nodes.items(.data); - - const start = node_datas[node].lhs; - const end = node_datas[node].rhs; - const gpa = astgen.gpa; + const data = astgen.tree.nodes.items(.data); const string_bytes = &astgen.string_bytes; const str_index = string_bytes.items.len; - // First line: do not append a newline. - var tok_i = start; - { - const slice = tree.tokenSlice(tok_i); - const line_bytes = slice[2..]; - try string_bytes.appendSlice(gpa, line_bytes); - tok_i += 1; - } - // Following lines: each line prepends a newline. - while (tok_i <= end) : (tok_i += 1) { - const slice = tree.tokenSlice(tok_i); - const line_bytes = slice[2..]; - try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1); - string_bytes.appendAssumeCapacity('\n'); - string_bytes.appendSliceAssumeCapacity(line_bytes); + var parser = std.zig.string_literal.multilineParser(string_bytes.writer(gpa)); + var tok_i = data[node].lhs; + while (tok_i <= data[node].rhs) : (tok_i += 1) { + try parser.line(astgen.tree.tokenSlice(tok_i)); } + const len = string_bytes.items.len - str_index; try string_bytes.append(gpa, 0); return IndexSlice{ diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 21f59f8e8398..9761f7dcf93f 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -1683,7 +1683,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, @@ -3813,6 +3813,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/number_literal.zig b/lib/std/zig/number_literal.zig index a4dc33eb91c3..40b8c44c176d 100644 --- a/lib/std/zig/number_literal.zig +++ b/lib/std/zig/number_literal.zig @@ -58,8 +58,83 @@ pub const Error = union(enum) { invalid_exponent_sign: usize, /// Period comes directly after exponent. period_after_exponent: usize, + + pub fn fmtWithSource(self: Error, bytes: []const u8) std.fmt.Formatter(formatErrorWithSource) { + return .{ .data = .{ .err = self, .bytes = bytes } }; + } + + pub fn noteWithSource(self: Error, bytes: []const u8) ?[]const u8 { + if (self == .leading_zero) { + const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null; + if (!is_float) return "use '0o' prefix for octal literals"; + } + return null; + } + + pub fn offset(self: Error) usize { + return switch (self) { + .leading_zero => 0, + .digit_after_base => 0, + .upper_case_base => |i| i, + .invalid_float_base => |i| i, + .repeated_underscore => |i| i, + .invalid_underscore_after_special => |i| i, + .invalid_digit => |e| e.i, + .invalid_digit_exponent => |i| i, + .duplicate_period => 0, + .duplicate_exponent => |i| i, + .exponent_after_underscore => |i| i, + .special_after_underscore => |i| i, + .trailing_special => |i| i, + .trailing_underscore => |i| i, + .invalid_character => |i| i, + .invalid_exponent_sign => |i| i, + .period_after_exponent => |i| i, + }; + } +}; + +const FormatWithSource = struct { + bytes: []const u8, + err: Error, }; +fn formatErrorWithSource( + self: FormatWithSource, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = options; + _ = fmt; + switch (self.err) { + .leading_zero => try writer.print("number '{s}' has leading zero", .{self.bytes}), + .digit_after_base => try writer.writeAll("expected a digit after base prefix"), + .upper_case_base => try writer.writeAll("base prefix must be lowercase"), + .invalid_float_base => try writer.writeAll("invalid base for float literal"), + .repeated_underscore => try writer.writeAll("repeated digit separator"), + .invalid_underscore_after_special => try writer.writeAll("expected digit before digit separator"), + .invalid_digit => |info| try writer.print("invalid digit '{c}' for {s} base", .{ self.bytes[info.i], @tagName(info.base) }), + .invalid_digit_exponent => |i| try writer.print("invalid digit '{c}' in exponent", .{self.bytes[i]}), + .duplicate_exponent => try writer.writeAll("duplicate exponent"), + .exponent_after_underscore => try writer.writeAll("expected digit before exponent"), + .special_after_underscore => |i| try writer.print("expected digit before '{c}'", .{self.bytes[i]}), + .trailing_special => |i| try writer.print("expected digit after '{c}'", .{self.bytes[i - 1]}), + .trailing_underscore => try writer.writeAll("trailing digit separator"), + .duplicate_period => try writer.writeAll("duplicate period"), + .invalid_character => try writer.writeAll("invalid character"), + .invalid_exponent_sign => |i| { + const hex = self.bytes.len >= 2 and self.bytes[0] == '0' and self.bytes[1] == 'x'; + if (hex) { + try writer.print("sign '{c}' cannot follow digit '{c}' in hex base", .{ self.bytes[i], self.bytes[i - 1] }); + } else { + try writer.print("sign '{c}' cannot follow digit '{c}' in current base", .{ self.bytes[i], self.bytes[i - 1] }); + } + }, + .period_after_exponent => try writer.writeAll("unexpected period after exponent"), + } +} + /// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString. /// Valid for any input. pub fn parseNumberLiteral(bytes: []const u8) Result { diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 716a9b90f01c..cfa4a103f62e 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -73,8 +73,74 @@ pub const Error = union(enum) { }, } } + + pub fn fmtWithSource(self: Error, raw_string: []const u8) std.fmt.Formatter(formatErrorWithSource) { + return .{ .data = .{ .err = self, .raw_string = raw_string } }; + } + + pub fn offset(self: Error) usize { + return switch (self) { + .invalid_escape_character => |i| i, + .expected_hex_digit => |i| i, + .empty_unicode_escape_sequence => |i| i, + .expected_hex_digit_or_rbrace => |i| i, + .invalid_unicode_codepoint => |i| i, + .expected_lbrace => |i| i, + .expected_rbrace => |i| i, + .expected_single_quote => |i| i, + .invalid_character => |i| i, + .empty_char_literal => 0, + }; + } }; +const FormatWithSource = struct { + raw_string: []const u8, + err: Error, +}; + +fn formatErrorWithSource( + self: FormatWithSource, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + _ = options; + _ = fmt; + 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.print("empty character literal", .{}); + }, + } +} + /// Asserts the slice starts and ends with single-quotes. /// Returns an error if there is not exactly one UTF-8 codepoint in between. pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral { @@ -282,7 +348,7 @@ test parseCharLiteral { /// Parses `bytes` as a Zig string literal and writes the result to the std.io.Writer type. /// Asserts `bytes` has '"' at beginning and end. -pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result { +pub fn parseWrite(writer: anytype, bytes: []const u8) !Result { assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"'); var index: usize = 1; @@ -347,3 +413,141 @@ test parseAlloc { try expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\""))); try expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\""))); } + +/// Parses one line at a time of a multiline Zig string literal to a std.io.Writer type. Does not append a null terminator. +pub fn MultilineParser(comptime Writer: type) type { + return struct { + writer: Writer, + first_line: bool, + + pub fn init(writer: Writer) @This() { + return .{ + .writer = writer, + .first_line = true, + }; + } + + /// Parse one line of a multiline string, writing the result to the writer prepending a newline if necessary. + /// + /// Asserts bytes begins with "\\". The line may be terminated with '\n' or "\r\n", but may not contain any interior newlines. + /// contain any interior newlines. + pub fn line(self: *@This(), bytes: []const u8) Writer.Error!void { + assert(bytes.len >= 2 and bytes[0] == '\\' and bytes[1] == '\\'); + if (self.first_line) { + self.first_line = false; + } else { + try self.writer.writeByte('\n'); + } + try self.writer.writeAll(bytes[2..]); + } + }; +} + +pub fn multilineParser(writer: anytype) MultilineParser(@TypeOf(writer)) { + return MultilineParser(@TypeOf(writer)).init(writer); +} + +test "parse multiline" { + // Varying newlines + { + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\bar"); + try std.testing.expectEqualStrings("foo\nbar", parsed.items); + } + + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\bar\n"); + try std.testing.expectEqualStrings("foo\nbar", parsed.items); + } + + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\bar\r\n"); + try std.testing.expectEqualStrings("foo\nbar", parsed.items); + } + + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo\n"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\bar"); + try std.testing.expectEqualStrings("foo\nbar", parsed.items); + } + + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo\r\n"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\bar"); + try std.testing.expectEqualStrings("foo\nbar", parsed.items); + } + } + + // Empty lines + { + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\"); + try std.testing.expectEqualStrings("", parsed.items); + try parser.line("\\\\"); + try std.testing.expectEqualStrings("\n", parsed.items); + try parser.line("\\\\foo"); + try std.testing.expectEqualStrings("\n\nfoo", parsed.items); + try parser.line("\\\\bar"); + try std.testing.expectEqualStrings("\n\nfoo\nbar", parsed.items); + } + + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\foo"); + try std.testing.expectEqualStrings("foo", parsed.items); + try parser.line("\\\\"); + try std.testing.expectEqualStrings("foo\n", parsed.items); + try parser.line("\\\\bar"); + try std.testing.expectEqualStrings("foo\n\nbar", parsed.items); + try parser.line("\\\\"); + try std.testing.expectEqualStrings("foo\n\nbar\n", parsed.items); + } + } + + // No escapes + { + var parsed = std.ArrayList(u8).init(std.testing.allocator); + defer parsed.deinit(); + const writer = parsed.writer(); + var parser = multilineParser(writer); + try parser.line("\\\\no \\n escape"); + try std.testing.expectEqualStrings("no \\n escape", parsed.items); + try parser.line("\\\\still no \\n escape"); + try std.testing.expectEqualStrings("no \\n escape\nstill no \\n escape", parsed.items); + } +} diff --git a/lib/std/zon.zig b/lib/std/zon.zig new file mode 100644 index 000000000000..232229eb3df8 --- /dev/null +++ b/lib/std/zon.zig @@ -0,0 +1,97 @@ +//! ZON serialization and deserialization. +//! +//! # ZON +//! ZON, or Zig Object Notation, is a subset* of Zig used for data storage. ZON contains no type +//! names. +//! +//! Supported Zig primitives: +//! * boolean literals +//! * number literals (including `nan` and `inf`) +//! * character literals +//! * enum literals +//! * `null` and `void` literals +//! * string literals +//! * multiline string literals +//! +//! Supported Zig containers: +//! * anonymous struct literals +//! * anonymous tuple literals +//! * slices +//! * notated as a reference to a tuple literal +//! * this syntax will likely be removed in the future, at which point ZON will not distinguish +//! between slices and tuples +//! +//! Here is an example ZON object: +//! ```zon +//! .{ +//! .a = 1.5, +//! .b = "hello, world!", +//! .c = .{ true, false }, +//! .d = &.{ 1, 2, 3 }, +//! } +//! ``` +//! +//! Individual primitives are also valid ZON, for example: +//! ```zon +//! "This string is a valid ZON object." +//! ``` +//! +//! \* ZON is not currently a true subset of Zig, because it supports `nan` and +//! `inf` literals, which Zig does not. +//! +//! # Deserialization +//! +//! The simplest way to deserialize ZON at runtime is `parseFromSlice`. (For reading ZON at +//! comptime, you can use `@import`.) +//! +//! If you need lower level control, or more detailed diagnostics, you can generate the AST yourself +//! with `std.zig.Ast.parse` and then deserialize it with: +//! * `parseFromAst` +//! * `parseFromAstNoAlloc` +//! +//! If you'd like to deserialize just part of an AST, you can use: +//! * `parseFromAstNode` +//! * `parseFromAstNodeNoAlloc` +//! +//! If you need lower level control than provided by this module, you can operate directly on the +//! results of `std.zig.Ast.parse`. +//! +//! +//! # Serialization +//! +//! The simplest way to serialize to ZON is to call `stringify`. +//! +//! If you need to serialize recursive types, the following functions are also provided: +//! * `stringifyMaxDepth` +//! * `stringifyArbitraryDepth` +//! +//! If you need more control over the serialization process, for example to control which fields are +//! serialized, configure fields individually, or to stringify ZON values that do not exist in +//! memory, you can use `Stringifier`. +//! +//! Note that serializing floats with more than 64 bits may result in a loss of precision +//! (see https://github.com/ziglang/zig/issues/1181). + +pub const ParseOptions = @import("zon/parse.zig").ParseOptions; +pub const ParseStatus = @import("zon/parse.zig").ParseStatus; +pub const parseFromSlice = @import("zon/parse.zig").parseFromSlice; +pub const parseFromAst = @import("zon/parse.zig").parseFromAst; +pub const parseFromAstNoAlloc = @import("zon/parse.zig").parseFromAstNoAlloc; +pub const parseFromAstNode = @import("zon/parse.zig").parseFromAstNode; +pub const parseFromAstNodeNoAlloc = @import("zon/parse.zig").parseFromAstNodeNoAlloc; +pub const parseFree = @import("zon/parse.zig").parseFree; + +pub const StringifierOptions = @import("zon/stringify.zig").StringifierOptions; +pub const StringifyValueOptions = @import("zon/stringify.zig").StringifyValueOptions; +pub const StringifyOptions = @import("zon/stringify.zig").StringifyOptions; +pub const StringifyContainerOptions = @import("zon/stringify.zig").StringifyContainerOptions; +pub const Stringifier = @import("zon/stringify.zig").Stringifier; +pub const stringify = @import("zon/stringify.zig").stringify; +pub const stringifyMaxDepth = @import("zon/stringify.zig").stringifyMaxDepth; +pub const stringifyArbitraryDepth = @import("zon/stringify.zig").stringifyArbitraryDepth; +pub const stringifier = @import("zon/stringify.zig").stringifier; + +test { + _ = @import("zon/parse.zig"); + _ = @import("zon/stringify.zig"); +} diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig new file mode 100644 index 000000000000..10f27dd6fd5c --- /dev/null +++ b/lib/std/zon/parse.zig @@ -0,0 +1,2845 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const Ast = std.zig.Ast; +const NodeIndex = std.zig.Ast.Node.Index; +const TokenIndex = std.zig.Ast.TokenIndex; +const Base = std.zig.number_literal.Base; +const StringLiteralError = std.zig.string_literal.Error; +const NumberLiteralError = std.zig.number_literal.Error; +const assert = std.debug.assert; +const ArrayListUnmanaged = std.ArrayListUnmanaged; + +gpa: Allocator, +ast: *const Ast, +status: ?*ParseStatus, +ident_buf: []u8, + +/// Configuration for the runtime parser. +pub const ParseOptions = 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, +}; + +/// Information about the success or failure of a parse. +pub const ParseStatus = union { + success: void, + failure: ParseFailure, +}; + +/// Information about a parse failure for presentation to the user via the format functions. +pub const ParseFailure = struct { + ast: *const Ast, + token: TokenIndex, + reason: Reason, + + const Reason = union(enum) { + out_of_memory: void, + expected_union: void, + expected_struct: void, + expected_primitive: struct { type_name: []const u8 }, + expected_enum: void, + expected_tuple_with_fields: struct { + fields: usize, + }, + expected_tuple: void, + expected_string: void, + cannot_represent: struct { type_name: []const u8 }, + negative_integer_zero: void, + invalid_string_literal: struct { + err: StringLiteralError, + }, + invalid_number_literal: struct { + err: NumberLiteralError, + }, + unexpected_field: struct { + fields: []const []const u8, + }, + missing_field: struct { + field_name: []const u8, + }, + duplicate_field: void, + type_expr: void, + address_of: void, + }; + + pub fn fmtLocation(self: *const @This()) std.fmt.Formatter(formatLocation) { + return .{ .data = self }; + } + + fn formatLocation( + self: *const @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + _ = fmt; + const l = self.ast.tokenLocation(0, self.token); + const offset = switch (self.reason) { + .invalid_string_literal => |r| r.err.offset(), + .invalid_number_literal => |r| r.err.offset(), + else => 0, + }; + try writer.print("{}:{}", .{ l.line + 1, l.column + 1 + offset }); + } + + pub fn fmtError(self: *const @This()) std.fmt.Formatter(formatError) { + return .{ .data = self }; + } + + fn formatError( + self: *const @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + _ = fmt; + return switch (self.reason) { + .out_of_memory => writer.writeAll("out of memory"), + .expected_union => writer.writeAll("expected union"), + .expected_struct => writer.writeAll("expected struct"), + .expected_primitive => |r| writer.print("expected {s}", .{r.type_name}), + .expected_enum => writer.writeAll("expected enum literal"), + .expected_tuple_with_fields => |r| { + const plural = if (r.fields == 1) "" else "s"; + try writer.print("expected tuple with {} field{s}", .{ r.fields, plural }); + }, + .expected_tuple => writer.writeAll("expected tuple"), + .expected_string => writer.writeAll("expected string"), + .cannot_represent => |r| writer.print("{s} cannot represent value", .{r.type_name}), + .negative_integer_zero => writer.writeAll("integer literal '-0' is ambiguous"), + .invalid_string_literal => |r| writer.print("{}", .{r.err.fmtWithSource(self.ast.tokenSlice(self.token))}), + .invalid_number_literal => |r| writer.print("{}", .{r.err.fmtWithSource(self.ast.tokenSlice(self.token))}), + .unexpected_field => |r| { + try writer.writeAll("unexpected field, "); + if (r.fields.len == 0) { + try writer.writeAll("no fields expected"); + } else { + try writer.writeAll("supported fields: "); + for (0..r.fields.len) |i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("{}", .{std.zig.fmtId(r.fields[i])}); + } + } + }, + .missing_field => |r| writer.print("missing required field {s}", .{r.field_name}), + .duplicate_field => writer.writeAll("duplicate field"), + .type_expr => writer.writeAll("ZON cannot contain type expressions"), + .address_of => writer.writeAll("ZON cannot take the address of a value"), + }; + } + + pub fn noteCount(self: *const @This()) usize { + switch (self.reason) { + .invalid_number_literal => |r| { + const source = self.ast.tokenSlice(self.token); + return if (r.err.noteWithSource(source) != null) 1 else 0; + }, + else => return 0, + } + } + + const FormatNote = struct { + failure: *const ParseFailure, + index: usize, + }; + + pub fn fmtNote(self: *const @This(), index: usize) std.fmt.Formatter(formatNote) { + return .{ .data = .{ .failure = self, .index = index } }; + } + + fn formatNote( + self: FormatNote, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + _ = fmt; + switch (self.failure.reason) { + .invalid_number_literal => |r| { + std.debug.assert(self.index == 0); + const source = self.failure.ast.tokenSlice(self.failure.token); + try writer.writeAll(r.err.noteWithSource(source).?); + return; + }, + else => {}, + } + + unreachable; + } + + pub fn format( + self: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = fmt; + _ = options; + try writer.print("{}: {}", .{ self.fmtLocation(), self.fmtError() }); + } +}; + +test "std.zon failure/oom formatting" { + const gpa = std.testing.allocator; + + // Generate a failure + var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); + defer ast.deinit(gpa); + var failing_allocator = std.testing.FailingAllocator.init(gpa, .{ + .fail_index = 0, + .resize_fail_index = 0, + }); + var status: ParseStatus = undefined; + try std.testing.expectError(error.OutOfMemory, parseFromAst( + []const u8, + failing_allocator.allocator(), + &ast, + &status, + .{}, + )); + + // Verify that we can format the entire failure. + const full = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(full); + try std.testing.expectEqualStrings("1:1: out of memory", full); + try std.testing.expectEqual(0, status.failure.noteCount()); + + // Verify that we can format the location by itself + const location = try std.fmt.allocPrint(gpa, "{}", .{status.failure.fmtLocation()}); + defer gpa.free(location); + try std.testing.expectEqualStrings("1:1", location); + + // Verify that we can format the reason by itself + const reason = try std.fmt.allocPrint(gpa, "{}", .{status.failure.fmtError()}); + defer std.testing.allocator.free(reason); + try std.testing.expectEqualStrings("out of memory", reason); +} + +/// Parses the given ZON source. +/// +/// Returns `error.OutOfMemory` on allocator failure, a `error.Type` error if the ZON could not be +/// deserialized into `T`, or `error.Syntax` error if the ZON was invalid. +/// +/// If detailed failure information is needed, see `parseFromAst`. +pub fn parseFromSlice( + /// The type to deserialize into. May only transitively contain the following supported types: + /// * bools + /// * fixed sized numeric types + /// * enums + /// * slices + /// * arrays + /// * structures + /// * unions + /// * optionals + /// * null + comptime T: type, + /// The allocator. Used to temporarily allocate an AST, and to allocate any parts of `T` that + /// require dynamic allocation. + gpa: Allocator, + /// The ZON source. + source: [:0]const u8, + /// Options for the parser. + comptime options: ParseOptions, +) error{ OutOfMemory, Type, Syntax }!T { + if (@inComptime()) { + // Happens if given e.g. @typeOf(null), the default error we get is hard + // to understand. + @compileError("Runtime parser cannot run at comptime."); + } + var ast = try std.zig.Ast.parse(gpa, source, .zon); + defer ast.deinit(gpa); + if (ast.errors.len != 0) return error.Syntax; + return parseFromAst(T, gpa, &ast, null, options); +} + +test "std.zon parseFromSlice syntax error" { + try std.testing.expectError(error.Syntax, parseFromSlice(u8, std.testing.allocator, ".{", .{})); +} + +/// Like `parseFromSlice`, but operates on an AST instead of on ZON source. Asserts that the AST's +/// `errors` field is empty. +/// +/// Returns `error.OutOfMemory` if allocation fails, or `error.Type` if the ZON could not be +/// deserialized into `T`. +/// +/// If `status` is not null, its success field will be set on success, and its failure field will be +/// set on failure. See `ParseFailure` for formatting ZON parse failures in a human readable +/// manner. For formatting AST errors, see `std.zig.Ast.renderError`. +pub fn parseFromAst(comptime T: type, gpa: Allocator, ast: *const Ast, status: ?*ParseStatus, comptime options: ParseOptions) error{ OutOfMemory, Type }!T { + assert(ast.errors.len == 0); + const data = ast.nodes.items(.data); + const root = data[0].lhs; + return parseFromAstNode(T, gpa, ast, root, status, options); +} + +/// Like `parseFromAst`, but does not take an allocator. +/// +/// Asserts at comptime that no value of type `T` requires dynamic allocation. +pub fn parseFromAstNoAlloc(comptime T: type, ast: *const Ast, status: ?*ParseStatus, comptime options: ParseOptions) error{Type}!T { + assert(ast.errors.len == 0); + const data = ast.nodes.items(.data); + const root = data[0].lhs; + return parseFromAstNodeNoAlloc(T, ast, root, status, options); +} + +test "std.zon parseFromAstNoAlloc" { + var ast = try std.zig.Ast.parse(std.testing.allocator, ".{ .x = 1.5, .y = 2.5 }", .zon); + defer ast.deinit(std.testing.allocator); + try std.testing.expectEqual(ast.errors.len, 0); + + const S = struct { x: f32, y: f32 }; + const found = try parseFromAstNoAlloc(S, &ast, null, .{}); + try std.testing.expectEqual(S{ .x = 1.5, .y = 2.5 }, found); +} + +/// Like `parseFromAst`, but the parse starts on `node` instead of on the root of the AST. +pub fn parseFromAstNode(comptime T: type, gpa: Allocator, ast: *const Ast, node: NodeIndex, status: ?*ParseStatus, comptime options: ParseOptions) error{ OutOfMemory, Type }!T { + assert(ast.errors.len == 0); + var ident_buf: [maxIdentLength(T)]u8 = undefined; + var parser = @This(){ + .gpa = gpa, + .ast = ast, + .status = status, + .ident_buf = &ident_buf, + }; + + // Attempt the parse, setting status and returning early if it fails + const result = parser.parseExpr(T, options, node) catch |err| switch (err) { + error.ParserOutOfMemory => return error.OutOfMemory, + error.Type => return error.Type, + }; + + // Set status to success and return the result + if (status) |s| s.* = .{ .success = {} }; + return result; +} + +/// Like `parseFromAstNode`, but does not take an allocator. +/// +/// Asserts at comptime that no value of type `T` requires dynamic allocation. +pub fn parseFromAstNodeNoAlloc(comptime T: type, ast: *const Ast, node: NodeIndex, status: ?*ParseStatus, comptime options: ParseOptions) error{Type}!T { + assert(ast.errors.len == 0); + if (comptime requiresAllocator(T)) { + @compileError(@typeName(T) ++ ": requires allocator"); + } + var buffer: [0]u8 = .{}; + var fba = std.heap.FixedBufferAllocator.init(&buffer); + return parseFromAstNode(T, fba.allocator(), ast, node, status, options) catch |e| switch (e) { + error.OutOfMemory => unreachable, // No allocations + else => |other| return other, + }; +} + +test "std.zon parseFromAstNode and parseFromAstNodeNoAlloc" { + const gpa = std.testing.allocator; + + var ast = try std.zig.Ast.parse(gpa, ".{ .vec = .{ .x = 1.5, .y = 2.5 } }", .zon); + defer ast.deinit(gpa); + try std.testing.expect(ast.errors.len == 0); + + const data = ast.nodes.items(.data); + const root = data[0].lhs; + var buf: [2]NodeIndex = undefined; + const init = ast.fullStructInit(&buf, root).?; + + const Vec2 = struct { x: f32, y: f32 }; + const parsed = try parseFromAstNode(Vec2, gpa, &ast, init.ast.fields[0], null, .{}); + const parsed_no_alloc = try parseFromAstNodeNoAlloc(Vec2, &ast, init.ast.fields[0], null, .{}); + try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed); + try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed_no_alloc); +} + +fn requiresAllocator(comptime T: type) bool { + // Keep in sync with parseFree, stringify, and requiresAllocator. + return switch (@typeInfo(T)) { + .Pointer => true, + .Array => |Array| requiresAllocator(Array.child), + .Struct => |Struct| inline for (Struct.fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .Union => |Union| inline for (Union.fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .Optional => |Optional| requiresAllocator(Optional.child), + else => false, + }; +} + +test "std.zon requiresAllocator" { + try std.testing.expect(!requiresAllocator(u8)); + try std.testing.expect(!requiresAllocator(f32)); + try std.testing.expect(!requiresAllocator(enum { foo })); + try std.testing.expect(!requiresAllocator(struct { f32 })); + try std.testing.expect(!requiresAllocator(struct { x: f32 })); + try std.testing.expect(!requiresAllocator([2]u8)); + try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(?f32)); + try std.testing.expect(!requiresAllocator(void)); + try std.testing.expect(!requiresAllocator(@TypeOf(null))); + + try std.testing.expect(requiresAllocator([]u8)); + try std.testing.expect(requiresAllocator(*struct { u8, u8 })); + try std.testing.expect(requiresAllocator([1][]const u8)); + try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(?[]u8)); +} + +fn maxIdentLength(comptime T: type) usize { + // Keep in sync with `parseExpr`. + comptime var max = 0; + switch (@typeInfo(T)) { + .Bool, .Int, .Float, .Null, .Void => {}, + .Pointer => |Pointer| max = comptime maxIdentLength(Pointer.child), + .Array => |Array| if (Array.len > 0) { + max = comptime maxIdentLength(Array.child); + }, + .Struct => |Struct| inline for (Struct.fields) |field| { + if (!Struct.is_tuple) { + max = @max(max, field.name.len); + } + max = @max(max, comptime maxIdentLength(field.type)); + }, + .Union => |Union| inline for (Union.fields) |field| { + max = @max(max, field.name.len); + max = @max(max, comptime maxIdentLength(field.type)); + }, + .Enum => |Enum| inline for (Enum.fields) |field| { + max = @max(max, field.name.len); + }, + .Optional => |Optional| max = comptime maxIdentLength(Optional.child), + else => unreachable, + } + return max; +} + +test "std.zon maxIdentLength" { + // Primitives + try std.testing.expectEqual(0, maxIdentLength(bool)); + try std.testing.expectEqual(0, maxIdentLength(u8)); + try std.testing.expectEqual(0, maxIdentLength(f32)); + try std.testing.expectEqual(0, maxIdentLength(@TypeOf(null))); + try std.testing.expectEqual(0, maxIdentLength(void)); + + // Arrays + try std.testing.expectEqual(0, maxIdentLength([0]u8)); + try std.testing.expectEqual(0, maxIdentLength([5]u8)); + try std.testing.expectEqual(3, maxIdentLength([5]struct { abc: f32 })); + try std.testing.expectEqual(0, maxIdentLength([0]struct { abc: f32 })); + + // Structs + try std.testing.expectEqual(0, maxIdentLength(struct {})); + try std.testing.expectEqual(1, maxIdentLength(struct { a: f32, b: f32 })); + try std.testing.expectEqual(3, maxIdentLength(struct { abc: f32, a: f32 })); + try std.testing.expectEqual(3, maxIdentLength(struct { a: f32, abc: f32 })); + + try std.testing.expectEqual(1, maxIdentLength(struct { a: struct { a: f32 }, b: struct { a: f32 } })); + try std.testing.expectEqual(3, maxIdentLength(struct { a: struct { abc: f32 }, b: struct { a: f32 } })); + try std.testing.expectEqual(3, maxIdentLength(struct { a: struct { a: f32 }, b: struct { abc: f32 } })); + + // Tuples + try std.testing.expectEqual(0, maxIdentLength(struct { f32, u32 })); + try std.testing.expectEqual(3, maxIdentLength(struct { struct { a: u32 }, struct { abc: u32 } })); + try std.testing.expectEqual(3, maxIdentLength(struct { struct { abc: u32 }, struct { a: u32 } })); + + // Unions + try std.testing.expectEqual(0, maxIdentLength(union {})); + + try std.testing.expectEqual(1, maxIdentLength(union { a: f32, b: f32 })); + try std.testing.expectEqual(3, maxIdentLength(union { abc: f32, a: f32 })); + try std.testing.expectEqual(3, maxIdentLength(union { a: f32, abc: f32 })); + + try std.testing.expectEqual(1, maxIdentLength(union { a: union { a: f32 }, b: union { a: f32 } })); + try std.testing.expectEqual(3, maxIdentLength(union { a: union { abc: f32 }, b: union { a: f32 } })); + try std.testing.expectEqual(3, maxIdentLength(union { a: union { a: f32 }, b: union { abc: f32 } })); + + // Enums + try std.testing.expectEqual(0, maxIdentLength(enum {})); + try std.testing.expectEqual(3, maxIdentLength(enum { a, abc })); + try std.testing.expectEqual(3, maxIdentLength(enum { abc, a })); + try std.testing.expectEqual(1, maxIdentLength(enum { a, b })); + + // Optionals + try std.testing.expectEqual(0, maxIdentLength(?u32)); + try std.testing.expectEqual(3, maxIdentLength(?struct { abc: u32 })); + + // Pointers + try std.testing.expectEqual(0, maxIdentLength(*u32)); + try std.testing.expectEqual(3, maxIdentLength(*struct { abc: u32 })); +} + +/// Frees values created by the runtime parser. +/// +/// 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 to free this type of value. +/// Untagged unions, for example, can be parsed but not freed. +pub fn parseFree(gpa: Allocator, value: anytype) void { + const Value = @TypeOf(value); + + // Keep in sync with parseFree, stringify, and requiresAllocator. + switch (@typeInfo(Value)) { + .Bool, .Int, .Float, .Enum => {}, + .Pointer => |Pointer| { + switch (Pointer.size) { + .One, .Many, .C => if (comptime requiresAllocator(Value)) { + @compileError(@typeName(Value) ++ ": parseFree cannot free non slice pointers"); + }, + .Slice => for (value) |item| { + parseFree(gpa, item); + }, + } + return gpa.free(value); + }, + .Array => for (value) |item| { + parseFree(gpa, item); + }, + .Struct => |Struct| inline for (Struct.fields) |field| { + parseFree(gpa, @field(value, field.name)); + }, + .Union => |Union| if (Union.tag_type == null) { + if (comptime requiresAllocator(Value)) { + @compileError(@typeName(Value) ++ ": parseFree cannot free untagged unions"); + } + } else switch (value) { + inline else => |_, tag| { + parseFree(gpa, @field(value, @tagName(tag))); + }, + }, + .Optional => if (value) |some| { + parseFree(gpa, some); + }, + .Void => {}, + .Null => {}, + else => @compileError(@typeName(Value) ++ ": parseFree cannot free this type"), + } +} + +fn parseExpr( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: NodeIndex, +) error{ ParserOutOfMemory, Type }!T { + // Check for address of up front so we can emit a friendlier error (otherwise it will just say + // that the type is wrong, which may be confusing.) + const tags = self.ast.nodes.items(.tag); + if (tags[node] == .address_of) { + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + return self.fail(token, .address_of); + } + + // Keep in sync with parseFree, stringify, and requiresAllocator. + switch (@typeInfo(T)) { + .Bool => return self.parseBool(node), + .Int, .Float => return self.parseNumber(T, node), + .Enum => return self.parseEnumLiteral(T, node), + .Pointer => return self.parsePointer(T, options, node), + .Array => return self.parseArray(T, options, node), + .Struct => |Struct| if (Struct.is_tuple) + return self.parseTuple(T, options, node) + else + return self.parseStruct(T, options, node), + .Union => return self.parseUnion(T, options, node), + .Optional => return self.parseOptional(T, options, node), + .Void => return self.parseVoid(node), + + else => @compileError(@typeName(T) ++ ": cannot parse this type"), + } +} + +fn parseVoid(self: @This(), node: NodeIndex) error{ ParserOutOfMemory, Type }!void { + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + const tags = self.ast.nodes.items(.tag); + const data = self.ast.nodes.items(.data); + switch (tags[node]) { + .block_two => if (data[node].lhs != 0 or data[node].rhs != 0) { + return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }); + }, + .block => if (data[node].lhs != data[node].rhs) { + return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }); + }, + else => return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }), + } +} + +test "std.zon void" { + const gpa = std.testing.allocator; + + const parsed: void = try parseFromSlice(void, gpa, "{}", .{}); + _ = parsed; + + // Freeing void is a noop, but it should compile! + const free: void = try parseFromSlice(void, gpa, "{}", .{}); + defer parseFree(gpa, free); + + // Other type + { + var ast = try std.zig.Ast.parse(gpa, "123", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(void, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected void", formatted); + } +} + +fn parseOptional(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Optional = @typeInfo(T).Optional; + + const tags = self.ast.nodes.items(.tag); + if (tags[node] == .identifier) { + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + const bytes = self.ast.tokenSlice(token); + if (std.mem.eql(u8, bytes, "null")) { + return null; + } + } + + return try self.parseExpr(Optional.child, options, node); +} + +test "std.zon optional" { + const gpa = std.testing.allocator; + + // Basic usage + { + const none = try parseFromSlice(?u32, gpa, "null", .{}); + try std.testing.expect(none == null); + const some = try parseFromSlice(?u32, gpa, "1", .{}); + try std.testing.expect(some.? == 1); + } + + // Deep free + { + const none = try parseFromSlice(?[]const u8, gpa, "null", .{}); + try std.testing.expect(none == null); + const some = try parseFromSlice(?[]const u8, gpa, "\"foo\"", .{}); + defer parseFree(gpa, some); + try std.testing.expectEqualStrings("foo", some.?); + } +} + +fn parseUnion(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Union = @typeInfo(T).Union; + const field_infos = Union.fields; + + if (field_infos.len == 0) { + @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); + } + + // Gather info on the fields + const field_indices = b: { + comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + inline for (field_infos, 0..) |field, i| { + kvs_list[i] = .{ field.name, i }; + } + break :b std.StaticStringMap(usize).initComptime(kvs_list); + }; + + // Parse the union + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + const tags = self.ast.nodes.items(.tag); + if (tags[node] == .enum_literal) { + // The union must be tagged for an enum literal to coerce to it + if (Union.tag_type == null) { + return self.fail(main_tokens[node], .expected_union); + } + + // Get the index of the named field. We don't use `parseEnum` here as + // the order of the enum and the order of the union might not match! + const field_index = b: { + const bytes = try self.parseIdent(T, token); + break :b field_indices.get(bytes) orelse + return self.failUnexpectedField(T, token); + }; + + // 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.fail(token, .expected_union); + + // Instantiate the union + return @unionInit(T, field_infos[i].name, {}); + }, + else => unreachable, // Can't be out of bounds + } + } else { + var buf: [2]NodeIndex = undefined; + const field_nodes = try self.fields(T, &buf, node); + + if (field_nodes.len != 1) { + return self.fail(token, .expected_union); + } + + // Fill in the field we found + const field_node = field_nodes[0]; + const field_token = self.ast.firstToken(field_node) - 2; + const field_index = b: { + const name = try self.parseIdent(T, field_token); + break :b field_indices.get(name) orelse + return self.failUnexpectedField(T, field_token); + }; + + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + const value = try self.parseExpr(field_infos[i].type, options, field_node); + return @unionInit(T, field_infos[i].name, value); + }, + else => unreachable, // Can't be out of bounds + } + } +} + +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 parseFromSlice(Tagged, gpa, ".{.x = 1.5}", .{}); + try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x); + const tagged_y = try parseFromSlice(Tagged, gpa, ".{.@\"y y\" = true}", .{}); + try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y); + const tagged_z_shorthand = try parseFromSlice(Tagged, gpa, ".z", .{}); + try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand); + const tagged_zz_shorthand = try parseFromSlice(Tagged, gpa, ".@\"z z\"", .{}); + try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand); + const tagged_z_explicit = try parseFromSlice(Tagged, gpa, ".{.z = {}}", .{}); + try std.testing.expectEqual(Tagged{ .z = {} }, tagged_z_explicit); + const tagged_zz_explicit = try parseFromSlice(Tagged, gpa, ".{.@\"z z\" = {}}", .{}); + try std.testing.expectEqual(Tagged{ .@"z z" = {} }, tagged_zz_explicit); + + const untagged_x = try parseFromSlice(Untagged, gpa, ".{.x = 1.5}", .{}); + try std.testing.expect(untagged_x.x == 1.5); + const untagged_y = try parseFromSlice(Untagged, gpa, ".{.@\"y y\" = true}", .{}); + try std.testing.expect(untagged_y.@"y y"); + } + + // Deep free + { + const Union = union(enum) { bar: []const u8, baz: bool }; + + const noalloc = try parseFromSlice(Union, gpa, ".{.baz = false}", .{}); + try std.testing.expectEqual(Union{ .baz = false }, noalloc); + + const alloc = try parseFromSlice(Union, gpa, ".{.bar = \"qux\"}", .{}); + defer parseFree(gpa, alloc); + try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); + } + + // Unknown field + { + const Union = union { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.z=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:4: unexpected field, supported fields: x, y", formatted); + } + + // Unknown field with name that's too long for parse + { + const Union = union { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.@\"abc\"=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:4: unexpected field, supported fields: x, y", formatted); + } + + // Extra field + { + const Union = union { x: f32, y: bool }; + var ast = try std.zig.Ast.parse(gpa, ".{.x = 1.5, .y = true}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected union", formatted); + } + + // No fields + { + const Union = union { x: f32, y: bool }; + var ast = try std.zig.Ast.parse(gpa, ".{}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected union", formatted); + } + + // Enum literals cannot coerce into untagged unions + { + const Union = union { x: void }; + var ast = try std.zig.Ast.parse(gpa, ".x", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected union", formatted); + } + + // Unknown field for enum literal coercion + { + const Union = union(enum) { x: void }; + var ast = try std.zig.Ast.parse(gpa, ".y", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: x", formatted); + } + + // Unknown field for enum literal coercion that's too long for parse + { + const Union = union(enum) { x: void }; + var ast = try std.zig.Ast.parse(gpa, ".@\"abc\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: x", formatted); + } + + // Non void field for enum literal coercion + { + const Union = union(enum) { x: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".x", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected union", formatted); + } + + // Union field with @ + { + const U = union(enum) { x: void }; + const tag = try parseFromSlice(U, gpa, ".@\"x\"", .{}); + try std.testing.expectEqual(@as(U, .x), tag); + const initializer = try parseFromSlice(U, gpa, ".{.@\"x\" = {}}", .{}); + try std.testing.expectEqual(U{ .x = {} }, initializer); + } +} + +fn elements( + self: @This(), + comptime T: type, + buf: *[2]NodeIndex, + node: NodeIndex, +) error{Type}![]const NodeIndex { + const main_tokens = self.ast.nodes.items(.main_token); + + // Attempt to parse as an array + if (self.ast.fullArrayInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.failTypeExpr(main_tokens[init.ast.type_expr]); + } + return init.ast.elements; + } + + // Attempt to parse as a struct with no fields + if (self.ast.fullStructInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.failTypeExpr(main_tokens[init.ast.type_expr]); + } + if (init.ast.fields.len == 0) { + return init.ast.fields; + } + } + + // Fail + return self.failExpectedContainer(T, main_tokens[node]); +} + +fn fields( + self: @This(), + comptime T: type, + buf: *[2]NodeIndex, + node: NodeIndex, +) error{Type}![]const NodeIndex { + const main_tokens = self.ast.nodes.items(.main_token); + + // Attempt to parse as a struct + if (self.ast.fullStructInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.failTypeExpr(main_tokens[init.ast.type_expr]); + } + return init.ast.fields; + } + + // Attempt to parse as a zero length array + if (self.ast.fullArrayInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.failTypeExpr(main_tokens[init.ast.type_expr]); + } + if (init.ast.elements.len != 0) { + return self.failExpectedContainer(T, main_tokens[node]); + } + return init.ast.elements; + } + + // Fail otherwise + return self.failExpectedContainer(T, main_tokens[node]); +} + +fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Struct = @typeInfo(T).Struct; + const field_infos = Struct.fields; + + // Gather info on the fields + const field_indices = b: { + comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + inline for (field_infos, 0..) |field, i| { + kvs_list[i] = .{ field.name, i }; + } + break :b std.StaticStringMap(usize).initComptime(kvs_list); + }; + + // Parse the struct + var buf: [2]NodeIndex = undefined; + const field_nodes = try self.fields(T, &buf, node); + + var result: T = undefined; + var field_found: [field_infos.len]bool = .{false} ** field_infos.len; + + // If we fail partway through, free all already initialized fields + var initialized: usize = 0; + errdefer if (options.free_on_error and field_infos.len > 0) { + for (field_nodes[0..initialized]) |initialized_field_node| { + const name_runtime = self.parseIdent(T, self.ast.firstToken(initialized_field_node) - 2) catch unreachable; + switch (field_indices.get(name_runtime) orelse continue) { + inline 0...(field_infos.len - 1) => |name_index| { + const name = field_infos[name_index].name; + parseFree(self.gpa, @field(result, name)); + }, + else => unreachable, // Can't be out of bounds + } + } + }; + + // Fill in the fields we found + for (field_nodes) |field_node| { + const name_token = self.ast.firstToken(field_node) - 2; + const i = b: { + const name = try self.parseIdent(T, name_token); + break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { + continue; + } else { + return self.failUnexpectedField(T, name_token); + }; + }; + + // We now know the array is not zero sized (assert this so the code compiles) + if (field_found.len == 0) unreachable; + + if (field_found[i]) { + return self.failDuplicateField(name_token); + } + field_found[i] = true; + + switch (i) { + inline 0...(field_infos.len - 1) => |j| @field(result, field_infos[j].name) = try self.parseExpr(field_infos[j].type, options, field_node), + 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 = Struct.fields[i]; + if (field_info.default_value) |default| { + const typed: *const field_info.type = @ptrCast(@alignCast(default)); + @field(result, field_info.name) = typed.*; + } else { + const main_tokens = self.ast.nodes.items(.main_token); + return self.failMissingField(field_infos[i].name, main_tokens[node]); + } + } + } + + return result; +} + +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 parseFromSlice(Vec0, gpa, ".{}", .{}); + try std.testing.expectEqual(Vec0{}, zero); + + const one = try parseFromSlice(Vec1, gpa, ".{.x = 1.2}", .{}); + try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); + + const two = try parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", .{}); + try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two); + + const three = try parseFromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", .{}); + 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 parseFromSlice(Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); + } + + // Unknown field + { + const Vec2 = struct { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .z=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:12: unexpected field, supported fields: x, y", formatted); + } + + // Unknown field too long for parse + { + const Vec2 = struct { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .@\"abc\"=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:12: unexpected field, supported fields: x, y", formatted); + } + + // Duplicate field + { + const Vec2 = struct { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .x=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:12: duplicate field", formatted); + } + + // Ignore unknown fields + { + const Vec2 = struct { x: f32, y: f32 = 2.0 }; + const parsed = try parseFromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", .{ + .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 ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .z=2.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:4: unexpected field, no fields expected", formatted); + } + + // Missing field + { + const Vec2 = struct { x: f32, y: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: missing required field y", formatted); + } + + // Default field + { + const Vec2 = struct { x: f32, y: f32 = 1.5 }; + const parsed = try parseFromSlice(Vec2, gpa, ".{.x = 1.2}", .{}); + try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); + } + + // 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 parseFromSlice(Vec0, gpa, ".{ .x = .x }", .{}); + 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 parseFromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", .{}); + try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); + } + + // Type expressions are not allowed + { + // Structs + { + const Empty = struct {}; + + var ast = try std.zig.Ast.parse(gpa, "Empty{}", .zon); + defer ast.deinit(gpa); + + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Empty, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + } + + // Arrays + { + var ast = try std.zig.Ast.parse(gpa, "[3]u8{1, 2, 3}", .zon); + defer ast.deinit(gpa); + + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + } + + // Slices + { + var ast = try std.zig.Ast.parse(gpa, "[]u8{1, 2, 3}", .zon); + defer ast.deinit(gpa); + + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + } + + // Tuples + { + const Tuple = struct { i32, i32, i32 }; + var ast = try std.zig.Ast.parse(gpa, "Tuple{1, 2, 3}", .zon); + defer ast.deinit(gpa); + + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + } + } +} + +fn parseTuple(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Struct = @typeInfo(T).Struct; + const field_infos = Struct.fields; + + var result: T = undefined; + + // Parse the struct + var buf: [2]NodeIndex = undefined; + const field_nodes = try self.elements(T, &buf, node); + + if (field_nodes.len != field_infos.len) { + const main_tokens = self.ast.nodes.items(.main_token); + return self.failExpectedContainer(T, main_tokens[node]); + } + + inline for (field_infos, field_nodes, 0..) |field_info, field_node, initialized| { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + inline for (0..field_infos.len) |i| { + if (i >= initialized) break; + parseFree(self.gpa, result[i]); + } + }; + + result[initialized] = try self.parseExpr(field_info.type, options, field_node); + } + + return result; +} + +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 parseFromSlice(Tuple0, gpa, ".{}", .{}); + try std.testing.expectEqual(Tuple0{}, zero); + + const one = try parseFromSlice(Tuple1, gpa, ".{1.2}", .{}); + try std.testing.expectEqual(Tuple1{1.2}, one); + + const two = try parseFromSlice(Tuple2, gpa, ".{1.2, true}", .{}); + try std.testing.expectEqual(Tuple2{ 1.2, true }, two); + + const three = try parseFromSlice(Tuple3, gpa, ".{1.2, false, 3}", .{}); + try std.testing.expectEqual(Tuple3{ 1.2, false, 3 }, three); + } + + // Deep free + { + const Tuple = struct { []const u8, []const u8 }; + const parsed = try parseFromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); + } + + // Extra field + { + const Tuple = struct { f32, bool }; + var ast = try std.zig.Ast.parse(gpa, ".{0.5, true, 123}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + } + + // Extra field + { + const Tuple = struct { f32, bool }; + var ast = try std.zig.Ast.parse(gpa, ".{0.5}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + } + + // Tuple with unexpected field names + { + const Tuple = struct { f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{.foo = 10.0}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 1 field", formatted); + } + + // Struct with missing field names + { + const Struct = struct { foo: f32 }; + var ast = try std.zig.Ast.parse(gpa, ".{10.0}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Struct, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected struct", formatted); + } +} + +fn parseArray(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Array = @typeInfo(T).Array; + // Parse the array + var array: T = undefined; + var buf: [2]NodeIndex = undefined; + const element_nodes = try self.elements(T, &buf, node); + + // Check if the size matches + if (element_nodes.len != Array.len) { + const main_tokens = self.ast.nodes.items(.main_token); + return self.failExpectedContainer(T, main_tokens[node]); + } + + // Parse the elements and return the array + for (&array, element_nodes, 0..) |*element, element_node, initialized| { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + for (array[0..initialized]) |initialized_item| { + parseFree(self.gpa, initialized_item); + } + }; + + element.* = try self.parseExpr(Array.child, options, element_node); + } + return array; +} + +// Test sizes 0 to 3 since small sizes get parsed differently +test "std.zon arrays and slices" { + // Issue: https://github.com/ziglang/zig/issues/20881 + if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; + + const gpa = std.testing.allocator; + + // Literals + { + // Arrays + { + const zero = try parseFromSlice([0]u8, gpa, ".{}", .{}); + try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); + + const one = try parseFromSlice([1]u8, gpa, ".{'a'}", .{}); + try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); + + const two = try parseFromSlice([2]u8, gpa, ".{'a', 'b'}", .{}); + try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two); + + const two_comma = try parseFromSlice([2]u8, gpa, ".{'a', 'b',}", .{}); + try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma); + + const three = try parseFromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", .{}); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); + + const sentinel = try parseFromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", .{}); + const expected_sentinel: [3:'z']u8 = .{ 'a', 'b', 'c' }; + try std.testing.expectEqualSlices(u8, &expected_sentinel, &sentinel); + } + + // Slice literals + { + const zero = try parseFromSlice([]const u8, gpa, ".{}", .{}); + defer parseFree(gpa, zero); + try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); + + const one = try parseFromSlice([]u8, gpa, ".{'a'}", .{}); + defer parseFree(gpa, one); + try std.testing.expectEqualSlices(u8, &.{'a'}, one); + + const two = try parseFromSlice([]const u8, gpa, ".{'a', 'b'}", .{}); + defer parseFree(gpa, two); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two); + + const two_comma = try parseFromSlice([]const u8, gpa, ".{'a', 'b',}", .{}); + defer parseFree(gpa, two_comma); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma); + + const three = try parseFromSlice([]u8, gpa, ".{'a', 'b', 'c'}", .{}); + defer parseFree(gpa, three); + try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three); + + const sentinel = try parseFromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", .{}); + defer parseFree(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 parseFromSlice([1][]const u8, gpa, ".{\"abc\"}", .{}); + defer parseFree(gpa, parsed); + const expected: [1][]const u8 = .{"abc"}; + try std.testing.expectEqualDeep(expected, parsed); + } + + // Slice literals + { + const parsed = try parseFromSlice([]const []const u8, gpa, ".{\"abc\"}", .{}); + defer parseFree(gpa, parsed); + const expected: []const []const u8 = &.{"abc"}; + try std.testing.expectEqualDeep(expected, parsed); + } + } + + // Sentinels and alignment + { + // Arrays + { + const sentinel = try parseFromSlice([1:2]u8, gpa, ".{1}", .{}); + 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 parseFromSlice([:2]align(4) u8, gpa, ".{1}", .{}); + defer parseFree(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 ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([0]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 0 fields", formatted); + } + + // Expect 1 find 2 + { + var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([1]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 1 field", formatted); + } + + // Expect 2 find 1 + { + var ast = try std.zig.Ast.parse(gpa, ".{'a'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([2]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + } + + // Expect 3 find 0 + { + var ast = try std.zig.Ast.parse(gpa, ".{}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected tuple with 3 fields", formatted); + } + + // Wrong inner type + { + // Array + { + var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([3]bool, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: expected bool", formatted); + } + + // Slice + { + var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]bool, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: expected bool", formatted); + } + } + + // Complete wrong type + { + // Array + { + var ast = try std.zig.Ast.parse(gpa, "'a'", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple with 3 fields", formatted); + } + + // Slice + { + var ast = try std.zig.Ast.parse(gpa, "'a'", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + } + + // Address of is not allowed (indirection for slices in ZON is implicit) + { + var ast = try std.zig.Ast.parse(gpa, "&.{'a', 'b', 'c'}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([3]bool, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: ZON cannot take the address of a value", formatted); + } +} + +fn parsePointer(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const tags = self.ast.nodes.items(.tag); + return switch (tags[node]) { + .string_literal => try self.parseStringLiteral(T, node), + .multiline_string_literal => try self.parseMultilineStringLiteral(T, node), + else => self.parseSlice(T, options, node), + }; +} + +fn parseSlice(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Ptr = @typeInfo(T).Pointer; + // Make sure we're working with a slice + switch (Ptr.size) { + .Slice => {}, + .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), + } + + // Parse the array literal + const main_tokens = self.ast.nodes.items(.main_token); + var buf: [2]NodeIndex = undefined; + const element_nodes = try self.elements(T, &buf, node); + + // Allocate the slice + const sentinel = if (Ptr.sentinel) |s| @as(*const Ptr.child, @ptrCast(s)).* else null; + const slice = self.gpa.allocWithOptions( + Ptr.child, + element_nodes.len, + Ptr.alignment, + sentinel, + ) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), + }; + errdefer self.gpa.free(slice); + + // Parse the elements and return the slice + for (slice, element_nodes, 0..) |*element, element_node, initialized| { + errdefer if (options.free_on_error) { + for (0..initialized) |i| { + parseFree(self.gpa, slice[i]); + } + }; + element.* = try self.parseExpr(Ptr.child, options, element_node); + } + return slice; +} + +fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const Pointer = @typeInfo(T).Pointer; + + if (Pointer.size != .Slice) { + @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); + } + + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + const raw = self.ast.tokenSlice(token); + + if (Pointer.child != u8 or !Pointer.is_const or Pointer.alignment != 1) { + return self.failExpectedContainer(T, token); + } + var buf = std.ArrayListUnmanaged(u8){}; + defer buf.deinit(self.gpa); + const parse_write_result = std.zig.string_literal.parseWrite( + buf.writer(self.gpa), + raw, + ) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(token), + }; + switch (parse_write_result) { + .success => {}, + .failure => |reason| return self.failInvalidStringLiteral(token, reason), + } + + if (Pointer.sentinel) |sentinel| { + if (@as(*const u8, @ptrCast(sentinel)).* != 0) { + return self.failExpectedContainer(T, token); + } + return buf.toOwnedSliceSentinel(self.gpa, 0) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(token), + }; + } + + return buf.toOwnedSlice(self.gpa) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(token), + }; +} + +fn parseMultilineStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { + const main_tokens = self.ast.nodes.items(.main_token); + + const Pointer = @typeInfo(T).Pointer; + + if (Pointer.size != .Slice) { + @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); + } + + if (Pointer.child != u8 or !Pointer.is_const or Pointer.alignment != 1) { + return self.failExpectedContainer(T, main_tokens[node]); + } + + var buf = std.ArrayListUnmanaged(u8){}; + defer buf.deinit(self.gpa); + const writer = buf.writer(self.gpa); + + var parser = std.zig.string_literal.multilineParser(writer); + const data = self.ast.nodes.items(.data); + var tok_i = data[node].lhs; + while (tok_i <= data[node].rhs) : (tok_i += 1) { + const token_slice = self.ast.tokenSlice(tok_i); + parser.line(token_slice) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(tok_i), + }; + } + + if (Pointer.sentinel) |sentinel| { + if (@as(*const u8, @ptrCast(sentinel)).* != 0) { + return self.failExpectedContainer(T, main_tokens[node]); + } + return buf.toOwnedSliceSentinel(self.gpa, 0) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), + }; + } else { + return buf.toOwnedSlice(self.gpa) catch |err| switch (err) { + error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), + }; + } +} + +test "std.zon string literal" { + const gpa = std.testing.allocator; + + // Basic string literal + { + const parsed = try parseFromSlice([]const u8, gpa, "\"abc\"", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed); + } + + // String literal with escape characters + { + const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\nc\"", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed); + } + + // String literal with embedded null + { + const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\x00c\"", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); + } + + // Passing string literal to a mutable slice + { + { + var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "\\\\abcd", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + } + + // Passing string literal to a array + { + { + var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([4:0]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple with 4 fields", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "\\\\abcd", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([4:0]u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple with 4 fields", formatted); + } + } + + // Zero termianted slices + { + { + const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\"abc\"", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualStrings("abc", parsed); + try std.testing.expectEqual(@as(u8, 0), parsed[3]); + } + + { + const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\\\\abc", .{}); + defer parseFree(gpa, parsed); + try std.testing.expectEqualStrings("abc", parsed); + try std.testing.expectEqual(@as(u8, 0), parsed[3]); + } + } + + // Other value terminated slices + { + { + var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([:1]const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "\\\\foo", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([:1]const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + } + + // Expecting string literal, getting something else + { + var ast = try std.zig.Ast.parse(gpa, "true", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected string", formatted); + } + + // Expecting string literal, getting an incompatible tuple + { + var ast = try std.zig.Ast.parse(gpa, ".{false}", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: expected u8", formatted); + } + + // Invalid string literal + { + var ast = try std.zig.Ast.parse(gpa, "\"\\a\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: invalid escape character: 'a'", formatted); + } + + // Slice wrong child type + { + { + var ast = try std.zig.Ast.parse(gpa, "\"a\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]const i8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "\\\\a", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]const i8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + } + + // Bad alignment + { + { + var ast = try std.zig.Ast.parse(gpa, "\"abc\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]align(2) const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "\\\\abc", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst([]align(2) const u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + } + } + + // Multi line strings + inline for (.{ []const u8, [:0]const u8 }) |String| { + // Nested + { + const S = struct { + message: String, + message2: String, + message3: String, + }; + const parsed = try parseFromSlice(S, gpa, + \\.{ + \\ .message = + \\ \\hello, world! + \\ + \\ \\this is a multiline string! + \\ \\ + \\ \\... + \\ + \\ , + \\ .message2 = + \\ \\this too...sort of. + \\ , + \\ .message3 = + \\ \\ + \\ \\and this. + \\} + , .{}); + defer parseFree(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); + } + } +} + +fn parseEnumLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { + const tags = self.ast.nodes.items(.tag); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + + switch (tags[node]) { + .enum_literal => { + // 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 bytes = try self.parseIdent(T, token); + return enum_tags.get(bytes) orelse + self.failUnexpectedField(T, token); + }, + else => return self.fail(token, .expected_enum), + } +} + +// Note that `parseIdent` may reuse the same buffer when called repeatedly, invalidating +// previous results. +// The resulting bytes may reference a buffer on `self` that can be reused in future calls to +// `parseIdent`. They should only be held onto temporarily. +fn parseIdent(self: @This(), T: type, token: TokenIndex) error{Type}![]const u8 { + var unparsed = self.ast.tokenSlice(token); + + if (unparsed[0] == '@' and unparsed[1] == '"') { + var fba = std.heap.FixedBufferAllocator.init(self.ident_buf); + const alloc = fba.allocator(); + var parsed = std.ArrayListUnmanaged(u8).initCapacity(alloc, self.ident_buf.len) catch unreachable; + + const raw = unparsed[1..unparsed.len]; + const result = std.zig.string_literal.parseWrite(parsed.writer(alloc), raw) catch |err| switch (err) { + // If it's too long for our preallocated buffer, it must be incorrect + error.OutOfMemory => return self.failUnexpectedField(T, token), + }; + switch (result) { + .failure => |reason| return self.failInvalidStringLiteral(token, reason), + .success => {}, + } + if (std.mem.indexOfScalar(u8, parsed.items, 0) != null) { + return self.failUnexpectedField(T, token); + } + return parsed.items; + } + + return unparsed; +} + +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 parseFromSlice(Enum, gpa, ".foo", .{})); + try std.testing.expectEqual(Enum.bar, try parseFromSlice(Enum, gpa, ".bar", .{})); + try std.testing.expectEqual(Enum.baz, try parseFromSlice(Enum, gpa, ".baz", .{})); + try std.testing.expectEqual(Enum.@"ab\nc", try parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", .{})); + + // Bad tag + { + var ast = try std.zig.Ast.parse(gpa, ".qux", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", formatted); + } + + // Bad tag that's too long for parser + { + var ast = try std.zig.Ast.parse(gpa, ".@\"foobarbaz\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", formatted); + } + + // Bad type + { + var ast = try std.zig.Ast.parse(gpa, "true", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected enum literal", formatted); + } + + // Test embedded nulls in an identifier + { + var ast = try std.zig.Ast.parse(gpa, ".@\"\\x00\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(enum { a }, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: a", formatted); + } +} + +fn fail(self: @This(), token: TokenIndex, reason: ParseFailure.Reason) error{Type} { + @setCold(true); + if (self.status) |s| s.* = .{ .failure = .{ + .ast = self.ast, + .token = token, + .reason = reason, + } }; + return error.Type; +} + +fn failOutOfMemory(self: *@This(), token: TokenIndex) error{ParserOutOfMemory} { + // Set our failure state, but ignore the type error because users may want to handle out of + // memory separately from other input errors + self.fail(token, .out_of_memory) catch {}; + + // We don't return error.OutOfMemory directly so that we can't forget to call this function, + // this error will be converted to error.OutOfMemory before returning to the user + return error.ParserOutOfMemory; +} + +fn failInvalidStringLiteral(self: @This(), token: TokenIndex, err: StringLiteralError) error{Type} { + @setCold(true); + return self.fail(token, .{ + .invalid_string_literal = .{ .err = err }, + }); +} + +fn failInvalidNumberLiteral(self: @This(), token: TokenIndex, err: NumberLiteralError) error{Type} { + @setCold(true); + return self.fail(token, .{ + .invalid_number_literal = .{ .err = err }, + }); +} + +fn failCannotRepresent(self: @This(), comptime T: type, token: TokenIndex) error{Type} { + @setCold(true); + return self.fail(token, .{ + .cannot_represent = .{ .type_name = @typeName(T) }, + }); +} + +fn failNegativeIntegerZero(self: @This(), token: TokenIndex) error{Type} { + @setCold(true); + return self.fail(token, .negative_integer_zero); +} + +fn failUnexpectedField(self: @This(), T: type, token: TokenIndex) error{Type} { + @setCold(true); + switch (@typeInfo(T)) { + .Struct, .Union, .Enum => return self.fail(token, .{ .unexpected_field = .{ + .fields = std.meta.fieldNames(T), + } }), + else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + } +} + +fn failExpectedContainer(self: @This(), T: type, token: TokenIndex) error{Type} { + @setCold(true); + switch (@typeInfo(T)) { + .Struct => |Struct| if (Struct.is_tuple) { + return self.fail(token, .{ .expected_tuple_with_fields = .{ + .fields = Struct.fields.len, + } }); + } else { + return self.fail(token, .expected_struct); + }, + .Union => return self.fail(token, .expected_union), + .Array => |Array| return self.fail(token, .{ .expected_tuple_with_fields = .{ + .fields = Array.len, + } }), + .Pointer => |Pointer| { + if (Pointer.child == u8 and + Pointer.size == .Slice and + Pointer.is_const and + (Pointer.sentinel == null or @as(*const u8, @ptrCast(Pointer.sentinel)).* == 0) and + Pointer.alignment == 1) + { + return self.fail(token, .expected_string); + } else { + return self.fail(token, .expected_tuple); + } + }, + else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + } +} + +fn failMissingField(self: @This(), name: []const u8, token: TokenIndex) error{Type} { + @setCold(true); + return self.fail(token, .{ .missing_field = .{ .field_name = name } }); +} + +fn failDuplicateField(self: @This(), token: TokenIndex) error{Type} { + @setCold(true); + return self.fail(token, .duplicate_field); +} + +fn failTypeExpr(self: @This(), token: TokenIndex) error{Type} { + @setCold(true); + return self.fail(token, .type_expr); +} + +fn parseBool(self: @This(), node: NodeIndex) error{Type}!bool { + const tags = self.ast.nodes.items(.tag); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node]; + switch (tags[node]) { + .identifier => { + const bytes = self.ast.tokenSlice(token); + const map = std.StaticStringMap(bool).initComptime(.{ + .{ "true", true }, + .{ "false", false }, + }); + if (map.get(bytes)) |value| { + return value; + } + }, + else => {}, + } + return self.fail(token, .{ .expected_primitive = .{ .type_name = "bool" } }); +} + +test "std.zon parse bool" { + const gpa = std.testing.allocator; + + // Correct floats + try std.testing.expectEqual(true, try parseFromSlice(bool, gpa, "true", .{})); + try std.testing.expectEqual(false, try parseFromSlice(bool, gpa, "false", .{})); + + // Errors + { + var ast = try std.zig.Ast.parse(gpa, " foo", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(bool, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:2: expected bool", formatted); + } + { + var ast = try std.zig.Ast.parse(gpa, "123", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(bool, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected bool", formatted); + } +} + +fn parseNumber( + self: @This(), + comptime T: type, + node: NodeIndex, +) error{Type}!T { + const main_tokens = self.ast.nodes.items(.main_token); + const num_lit_node = self.numLitNode(node); + const tags = self.ast.nodes.items(.tag); + switch (tags[num_lit_node]) { + .number_literal => return self.parseNumberLiteral(T, node), + .char_literal => return self.parseCharLiteral(T, node), + .identifier => switch (@typeInfo(T)) { + .Float => { + const token = main_tokens[num_lit_node]; + const bytes = self.ast.tokenSlice(token); + const Ident = enum { inf, nan }; + const map = std.StaticStringMap(Ident).initComptime(.{ + .{ "inf", .inf }, + .{ "nan", .nan }, + }); + if (map.get(bytes)) |value| { + switch (value) { + .inf => if (self.isNegative(node)) { + return -std.math.inf(T); + } else { + return std.math.inf(T); + }, + .nan => return std.math.nan(T), + } + } + }, + else => {}, + }, + else => {}, + } + return self.fail(main_tokens[node], .{ + .expected_primitive = .{ .type_name = @typeName(T) }, + }); +} + +fn parseNumberLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { + const num_lit_node = self.numLitNode(node); + const main_tokens = self.ast.nodes.items(.main_token); + const num_lit_token = main_tokens[num_lit_node]; + const token_bytes = self.ast.tokenSlice(num_lit_token); + const number = std.zig.number_literal.parseNumberLiteral(token_bytes); + + switch (number) { + .int => |int| return self.applySignToInt(T, node, int), + .big_int => |base| return self.parseBigNumber(T, node, base), + .float => return self.parseFloat(T, node), + .failure => |reason| return self.failInvalidNumberLiteral(main_tokens[node], reason), + } +} + +fn applySignToInt(self: @This(), comptime T: type, node: NodeIndex, int: anytype) error{Type}!T { + const main_tokens = self.ast.nodes.items(.main_token); + if (self.isNegative(node)) { + if (int == 0) { + return self.failNegativeIntegerZero(main_tokens[node]); + } + switch (@typeInfo(T)) { + .Int => |int_type| switch (int_type.signedness) { + .signed => { + const In = @TypeOf(int); + if (std.math.maxInt(In) > std.math.maxInt(T) and int == @as(In, std.math.maxInt(T)) + 1) { + return std.math.minInt(T); + } + + return -(std.math.cast(T, int) orelse return self.failCannotRepresent(T, main_tokens[node])); + }, + .unsigned => return self.failCannotRepresent(T, main_tokens[node]), + }, + .Float => return -@as(T, @floatFromInt(int)), + else => @compileError("internal error: expected numeric type"), + } + } else { + switch (@typeInfo(T)) { + .Int => return std.math.cast(T, int) orelse + self.failCannotRepresent(T, main_tokens[node]), + .Float => return @as(T, @floatFromInt(int)), + else => @compileError("internal error: expected numeric type"), + } + } +} + +fn parseBigNumber( + self: @This(), + comptime T: type, + node: NodeIndex, + base: Base, +) error{Type}!T { + switch (@typeInfo(T)) { + .Int => return self.parseBigInt(T, node, base), + .Float => { + const result = @as(T, @floatCast(try self.parseFloat(f128, node))); + if (std.math.isNegativeZero(result)) { + const main_tokens = self.ast.nodes.items(.main_token); + return self.failNegativeIntegerZero(main_tokens[node]); + } + return result; + }, + else => @compileError("internal error: expected integer or float type"), + } +} + +fn parseBigInt(self: @This(), comptime T: type, node: NodeIndex, base: Base) error{Type}!T { + const num_lit_node = self.numLitNode(node); + const main_tokens = self.ast.nodes.items(.main_token); + const num_lit_token = main_tokens[num_lit_node]; + const prefix_offset: usize = if (base == .decimal) 0 else 2; + const bytes = self.ast.tokenSlice(num_lit_token)[prefix_offset..]; + const result = if (self.isNegative(node)) + std.fmt.parseIntWithSign(T, u8, bytes, @intFromEnum(base), .neg) + else + std.fmt.parseIntWithSign(T, u8, bytes, @intFromEnum(base), .pos); + return result catch |err| switch (err) { + error.InvalidCharacter => unreachable, + error.Overflow => return self.failCannotRepresent(T, main_tokens[node]), + }; +} + +fn parseFloat( + self: @This(), + comptime T: type, + node: NodeIndex, +) error{Type}!T { + const num_lit_node = self.numLitNode(node); + const main_tokens = self.ast.nodes.items(.main_token); + const num_lit_token = main_tokens[num_lit_node]; + const bytes = self.ast.tokenSlice(num_lit_token); + const Float = if (@typeInfo(T) == .Float) T else f128; + const unsigned_float = std.fmt.parseFloat(Float, bytes) catch unreachable; // Already validated + const result = if (self.isNegative(node)) -unsigned_float else unsigned_float; + switch (@typeInfo(T)) { + .Float => return @as(T, @floatCast(result)), + .Int => return intFromFloatExact(T, result) orelse + return self.failCannotRepresent(T, main_tokens[node]), + else => @compileError("internal error: expected integer or float type"), + } +} + +fn parseCharLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { + const num_lit_node = self.numLitNode(node); + const main_tokens = self.ast.nodes.items(.main_token); + const num_lit_token = main_tokens[num_lit_node]; + const token_bytes = self.ast.tokenSlice(num_lit_token); + const char = std.zig.string_literal.parseCharLiteral(token_bytes).success; + return self.applySignToInt(T, node, char); +} + +fn isNegative(self: *const @This(), node: NodeIndex) bool { + const tags = self.ast.nodes.items(.tag); + return tags[node] == .negation; +} + +fn numLitNode(self: *const @This(), node: NodeIndex) NodeIndex { + if (self.isNegative(node)) { + const data = self.ast.nodes.items(.data); + return data[node].lhs; + } else { + return node; + } +} + +fn intFromFloatExact(comptime T: type, value: anytype) ?T { + switch (@typeInfo(@TypeOf(value))) { + .Float => {}, + else => @compileError(@typeName(@TypeOf(value)) ++ " is not a runtime floating point type"), + } + switch (@typeInfo(T)) { + .Int => {}, + else => @compileError(@typeName(T) ++ " is not a runtime integer type"), + } + + if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { + return null; + } + + if (std.math.isNan(value) or std.math.trunc(value) != value) { + return null; + } + + return @as(T, @intFromFloat(value)); +} + +test "std.zon 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 parseFromSlice(u8, gpa, "10", .{})); + try std.testing.expectEqual(@as(i16, 24), try parseFromSlice(i16, gpa, "24", .{})); + try std.testing.expectEqual(@as(i14, -4), try parseFromSlice(i14, gpa, "-4", .{})); + try std.testing.expectEqual(@as(i32, -123), try parseFromSlice(i32, gpa, "-123", .{})); + + // Test limits + try std.testing.expectEqual(@as(i8, 127), try parseFromSlice(i8, gpa, "127", .{})); + try std.testing.expectEqual(@as(i8, -128), try parseFromSlice(i8, gpa, "-128", .{})); + + // Test characters + try std.testing.expectEqual(@as(u8, 'a'), try parseFromSlice(u8, gpa, "'a'", .{})); + try std.testing.expectEqual(@as(u8, 'z'), try parseFromSlice(u8, gpa, "'z'", .{})); + try std.testing.expectEqual(@as(i16, -'a'), try parseFromSlice(i16, gpa, "-'a'", .{})); + try std.testing.expectEqual(@as(i16, -'z'), try parseFromSlice(i16, gpa, "-'z'", .{})); + + // Test big integers + try std.testing.expectEqual( + @as(u65, 36893488147419103231), + try parseFromSlice(u65, gpa, "36893488147419103231", .{}), + ); + try std.testing.expectEqual( + @as(u65, 36893488147419103231), + try parseFromSlice(u65, gpa, "368934_881_474191032_31", .{}), + ); + + // Test big integer limits + try std.testing.expectEqual( + @as(i66, 36893488147419103231), + try parseFromSlice(i66, gpa, "36893488147419103231", .{}), + ); + try std.testing.expectEqual( + @as(i66, -36893488147419103232), + try parseFromSlice(i66, gpa, "-36893488147419103232", .{}), + ); + { + var ast = try std.zig.Ast.parse(gpa, "36893488147419103232", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(i66, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: i66 cannot represent value", formatted); + } + { + var ast = try std.zig.Ast.parse(gpa, "-36893488147419103233", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(i66, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: i66 cannot represent value", formatted); + } + + // Test parsing whole number floats as integers + try std.testing.expectEqual(@as(i8, -1), try parseFromSlice(i8, gpa, "-1.0", .{})); + try std.testing.expectEqual(@as(i8, 123), try parseFromSlice(i8, gpa, "123.0", .{})); + + // Test non-decimal integers + try std.testing.expectEqual(@as(i16, 0xff), try parseFromSlice(i16, gpa, "0xff", .{})); + try std.testing.expectEqual(@as(i16, -0xff), try parseFromSlice(i16, gpa, "-0xff", .{})); + try std.testing.expectEqual(@as(i16, 0o77), try parseFromSlice(i16, gpa, "0o77", .{})); + try std.testing.expectEqual(@as(i16, -0o77), try parseFromSlice(i16, gpa, "-0o77", .{})); + try std.testing.expectEqual(@as(i16, 0b11), try parseFromSlice(i16, gpa, "0b11", .{})); + try std.testing.expectEqual(@as(i16, -0b11), try parseFromSlice(i16, gpa, "-0b11", .{})); + + // Test non-decimal big integers + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( + u65, + gpa, + "0x1ffffffffffffffff", + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "0x1ffffffffffffffff", + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "-0x1ffffffffffffffff", + .{}, + )); + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( + u65, + gpa, + "0o3777777777777777777777", + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "0o3777777777777777777777", + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "-0o3777777777777777777777", + .{}, + )); + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( + u65, + gpa, + "0b11111111111111111111111111111111111111111111111111111111111111111", + .{}, + )); + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "0b11111111111111111111111111111111111111111111111111111111111111111", + .{}, + )); + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + i66, + gpa, + "-0b11111111111111111111111111111111111111111111111111111111111111111", + .{}, + )); + + // Number with invalid character in the middle + { + var ast = try std.zig.Ast.parse(gpa, "32a32", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: invalid digit 'a' for decimal base", formatted); + } + + // Failing to parse as int + { + var ast = try std.zig.Ast.parse(gpa, "true", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected u8", formatted); + } + + // Failing because an int is out of range + { + var ast = try std.zig.Ast.parse(gpa, "256", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + } + + // Failing because a negative int is out of range + { + var ast = try std.zig.Ast.parse(gpa, "-129", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: i8 cannot represent value", formatted); + } + + // Failing because an unsigned int is negative + { + var ast = try std.zig.Ast.parse(gpa, "-1", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + } + + // Failing because a float is non-whole + { + var ast = try std.zig.Ast.parse(gpa, "1.5", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + } + + // Failing because a float is negative + { + var ast = try std.zig.Ast.parse(gpa, "-1.0", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + } + + // Negative integer zero + { + var ast = try std.zig.Ast.parse(gpa, "-0", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: integer literal '-0' is ambiguous", formatted); + } + + // Negative integer zero casted to float + { + var ast = try std.zig.Ast.parse(gpa, "-0", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: integer literal '-0' is ambiguous", formatted); + } + + // Negative float 0 is allowed + try std.testing.expect(std.math.isNegativeZero(try parseFromSlice(f32, gpa, "-0.0", .{}))); + try std.testing.expect(std.math.isPositiveZero(try parseFromSlice(f32, gpa, "0.0", .{}))); + + // Double negation is not allowed + { + var ast = try std.zig.Ast.parse(gpa, "--2", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected i8", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "--2.0", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected f32", formatted); + } + + // Invalid int literal + { + var ast = try std.zig.Ast.parse(gpa, "0xg", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:3: invalid digit 'g' for hex base", formatted); + } + + // Notes on invalid int literal + { + var ast = try std.zig.Ast.parse(gpa, "0123", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); + try std.testing.expectFmt("1:1: number '0123' has leading zero", "{}", .{status.failure}); + try std.testing.expectEqual(1, status.failure.noteCount()); + try std.testing.expectFmt("use '0o' prefix for octal literals", "{}", .{status.failure.fmtNote(0)}); + } +} + +test "std.zon parse float" { + const gpa = std.testing.allocator; + + // Test decimals + try std.testing.expectEqual(@as(f16, 0.5), try parseFromSlice(f16, gpa, "0.5", .{})); + try std.testing.expectEqual(@as(f32, 123.456), try parseFromSlice(f32, gpa, "123.456", .{})); + try std.testing.expectEqual(@as(f64, -123.456), try parseFromSlice(f64, gpa, "-123.456", .{})); + try std.testing.expectEqual(@as(f128, 42.5), try parseFromSlice(f128, gpa, "42.5", .{})); + + // Test whole numbers with and without decimals + try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5.0", .{})); + try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5", .{})); + try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102.0", .{})); + try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102", .{})); + + // Test characters and negated characters + try std.testing.expectEqual(@as(f32, 'a'), try parseFromSlice(f32, gpa, "'a'", .{})); + try std.testing.expectEqual(@as(f32, 'z'), try parseFromSlice(f32, gpa, "'z'", .{})); + try std.testing.expectEqual(@as(f32, -'z'), try parseFromSlice(f32, gpa, "-'z'", .{})); + + // Test big integers + try std.testing.expectEqual( + @as(f32, 36893488147419103231), + try parseFromSlice(f32, gpa, "36893488147419103231", .{}), + ); + try std.testing.expectEqual( + @as(f32, -36893488147419103231), + try parseFromSlice(f32, gpa, "-36893488147419103231", .{}), + ); + try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try parseFromSlice( + f128, + gpa, + "0x1ffffffffffffffff", + .{}, + )); + try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try parseFromSlice( + f32, + gpa, + "0x1ffffffffffffffff", + .{}, + )); + + // Exponents, underscores + try std.testing.expectEqual(@as(f32, 123.0E+77), try parseFromSlice(f32, gpa, "12_3.0E+77", .{})); + + // Hexadecimal + try std.testing.expectEqual(@as(f32, 0x103.70p-5), try parseFromSlice(f32, gpa, "0x103.70p-5", .{})); + try std.testing.expectEqual(@as(f32, -0x103.70), try parseFromSlice(f32, gpa, "-0x103.70", .{})); + try std.testing.expectEqual( + @as(f32, 0x1234_5678.9ABC_CDEFp-10), + try parseFromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", .{}), + ); + + // inf, nan + try std.testing.expect(std.math.isPositiveInf(try parseFromSlice(f32, gpa, "inf", .{}))); + try std.testing.expect(std.math.isNegativeInf(try parseFromSlice(f32, gpa, "-inf", .{}))); + try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "nan", .{}))); + try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "-nan", .{}))); + + // Bad identifier as float + { + var ast = try std.zig.Ast.parse(gpa, "foo", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected f32", formatted); + } + + { + var ast = try std.zig.Ast.parse(gpa, "-foo", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected f32", formatted); + } + + // Non float as float + { + var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); + defer ast.deinit(gpa); + var status: ParseStatus = undefined; + try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); + const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); + defer gpa.free(formatted); + try std.testing.expectEqualStrings("1:1: expected f32", formatted); + } +} + +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.Type, parseFromSlice(Struct, std.testing.allocator, + \\.{ + \\ .x = "hello", + \\ .y = "world", + \\ .z = "fail", + \\} + , .{})); + } + + // Test freeing partially allocated tuples + { + const Struct = struct { + []const u8, + []const u8, + bool, + }; + try std.testing.expectError(error.Type, parseFromSlice(Struct, std.testing.allocator, + \\.{ + \\ "hello", + \\ "world", + \\ "fail", + \\} + , .{})); + } + + // Test freeing structs with missing fields + { + const Struct = struct { + x: []const u8, + y: bool, + }; + try std.testing.expectError(error.Type, parseFromSlice(Struct, std.testing.allocator, + \\.{ + \\ .x = "hello", + \\} + , .{})); + } + + // Test freeing partially allocated arrays + { + try std.testing.expectError(error.Type, parseFromSlice([3][]const u8, std.testing.allocator, + \\.{ + \\ "hello", + \\ false, + \\ false, + \\} + , .{})); + } + + // Test freeing partially allocated slices + { + try std.testing.expectError(error.Type, parseFromSlice([][]const u8, std.testing.allocator, + \\.{ + \\ "hello", + \\ "world", + \\ false, + \\} + , .{})); + } + + // 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 parseFromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", .{})).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 parseFromSlice(union { x: []const u8 }, std.testing.allocator, ".{ .x = \"foo\" }", .{}); + defer parseFree(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 parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", .{ + .free_on_error = false, + }); + defer parseFree(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 parseFromSlice(S, std.testing.allocator, ".{ .a = .{ .x = \"foo\" }, .b = true }", .{ + .free_on_error = false, + }); + defer parseFree(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 parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", .{ + .free_on_error = false, + }); + defer parseFree(std.testing.allocator, result[0].x); + defer parseFree(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 parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", .{ + .free_on_error = false, + }); + defer std.testing.allocator.free(result); + defer parseFree(std.testing.allocator, result[0].x); + defer parseFree(std.testing.allocator, result[1].x); + try std.testing.expectEqualStrings("foo", result[0].x); + try std.testing.expectEqualStrings("bar", result[1].x); + } +} diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig new file mode 100644 index 000000000000..81ebc065f84b --- /dev/null +++ b/lib/std/zon/stringify.zig @@ -0,0 +1,1920 @@ +const std = @import("std"); + +/// Configuration for stringification. +/// +/// See `StringifyOptions` for more details. +pub const StringifierOptions = struct { + /// If false, only syntactically necessary whitespace is emitted. + whitespace: bool = true, +}; + +/// Options for stringification of an individual value. +/// +/// See `StringifyOptions` for more details. +pub const StringifyValueOptions = struct { + emit_utf8_codepoints: bool = false, + emit_strings_as_containers: bool = false, + emit_default_optional_fields: bool = true, +}; + +/// All stringify options. +pub const StringifyOptions = struct { + /// If false, all whitespace is emitted. Otherwise, whitespace is emitted in the standard Zig + /// style when possible. + whitespace: bool = true, + /// If true, unsigned integers with <= 21 bits are written as their corresponding UTF8 codepoint + /// instead of a numeric literal if one exists. + emit_utf8_codepoints: bool = false, + /// If true, slices of u8s, and pointers to arrays of u8s are serialized as containers. + /// Otherwise they are serialized as string literals. + emit_strings_as_containers: bool = false, + /// If false, struct fields are not written if they are equal to their default value. Comparison + /// is done by `std.meta.eql`. + emit_default_optional_fields: bool = true, +}; + +/// Options for manual serializaation of container types. +pub const StringifyContainerOptions = struct { + /// The whitespace style that should be used for this container. Ignored if whitespace is off. + whitespace_style: union(enum) { + /// If true, wrap every field/item. If false do not. + wrap: bool, + /// Automatically decide whether to wrap or not based on the number of fields. Following + /// the standard rule of thumb, containers with more than two fields are wrapped. + fields: usize, + } = .{ .wrap = true }, + + fn shouldWrap(self: StringifyContainerOptions) bool { + return switch (self.whitespace_style) { + .wrap => |wrap| wrap, + .fields => |fields| fields > 2, + }; + } +}; + +/// Serialize the given value to ZON. +/// +/// It is asserted at comptime that `@TypeOf(val)` is not a recursive type. +pub fn stringify( + /// The value to serialize. May only transitively contain the following supported types: + /// * bools + /// * fixed sized numeric types + /// * exhaustive enums, enum literals + /// * Non-exhaustive enums may hold values that have no literal representation, and + /// therefore cannot be stringified in a way that allows round trips back through the + /// parser. There are plans to resolve this in the future. + /// * slices + /// * arrays + /// * structures + /// * tagged unions + /// * optionals + /// * null + val: anytype, + comptime options: StringifyOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var serializer = stringifier(writer, .{ + .whitespace = options.whitespace, + }); + try serializer.value(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }); +} + +/// Like `stringify`, but recursive types are allowed. +/// +/// Returns `error.MaxDepth` if `depth` is exceeded. +pub fn stringifyMaxDepth(val: anytype, comptime options: StringifyOptions, writer: anytype, depth: usize) Stringifier(@TypeOf(writer)).MaxDepthError!void { + var serializer = stringifier(writer, .{ + .whitespace = options.whitespace, + }); + try serializer.valueMaxDepth(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }, depth); +} + +/// Like `stringify`, but recursive types are allowed. +/// +/// It is the caller's responsibility to ensure that `val` does not contain cycles. +pub fn stringifyArbitraryDepth(val: anytype, comptime options: StringifyOptions, writer: anytype) @TypeOf(writer).Error!void { + var serializer = stringifier(writer, .{ + .whitespace = options.whitespace, + }); + try serializer.valueArbitraryDepth(val, .{ + .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_strings_as_containers = options.emit_strings_as_containers, + .emit_default_optional_fields = options.emit_default_optional_fields, + }); +} + +const RecursiveTypeBuffer = [32]type; + +fn typeIsRecursive(comptime T: type) bool { + comptime var buf: RecursiveTypeBuffer = undefined; + return comptime typeIsRecursiveImpl(T, buf[0..0]); +} + +fn typeIsRecursiveImpl(comptime T: type, comptime visited_arg: []type) bool { + comptime var visited = visited_arg; + + // Check if we've already seen this type + inline for (visited) |found| { + if (T == found) { + return true; + } + } + + // Add this type to the stack + if (visited.len >= @typeInfo(RecursiveTypeBuffer).Array.len) { + @compileError("recursion limit"); + } + visited.ptr[visited.len] = T; + visited.len += 1; + + // Recurse + switch (@typeInfo(T)) { + .Pointer => |Pointer| return typeIsRecursiveImpl(Pointer.child, visited), + .Array => |Array| return typeIsRecursiveImpl(Array.child, visited), + .Struct => |Struct| inline for (Struct.fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) { + return true; + } + }, + .Union => |Union| inline for (Union.fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) { + return true; + } + }, + .Optional => |Optional| return typeIsRecursiveImpl(Optional.child, visited), + else => {}, + } + return false; +} + +test "std.zon typeIsRecursive" { + try std.testing.expect(!typeIsRecursive(bool)); + try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); + try std.testing.expect(!typeIsRecursive(struct { i32, i32 })); + try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() })); + try std.testing.expect(typeIsRecursive(struct { + a: struct { + const A = @This(); + b: struct { + c: *struct { + a: ?A, + }, + }, + }, + })); + try std.testing.expect(typeIsRecursive(struct { + a: [3]*@This(), + })); + try std.testing.expect(typeIsRecursive(struct { + a: union { a: i32, b: *@This() }, + })); +} + +fn checkValueDepth(val: anytype, depth: usize) error{MaxDepth}!void { + if (depth == 0) return error.MaxDepth; + 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.MaxDepth, 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 }})); +} + +/// Lower level control over stringification, you can create a new instance with `stringifier`. +/// +/// Useful when you want control over which fields/items are stringified, how they're represented, +/// or want to write a ZON object that does not exist in memory. +/// +/// You can serialize values with `value`. To serialize recursive types, the following are provided: +/// * `valueMaxDepth` +/// * `valueArbitraryDepth` +/// +/// You can also serialize values using specific notations: +/// * `int` +/// * `float` +/// * `utf8Codepoint` +/// * `slice` +/// * `sliceMaxDepth` +/// * `sliceArbitraryDepth` +/// * `string` +/// * `multilineString` +/// +/// For manual serialization of containers, see: +/// * `startStruct` +/// * `startTuple` +/// * `startSlice` +/// +/// # Example +/// ```zig +/// var serializer = stringifier(writer, .{}); +/// var vec2 = try serializer.startStruct(.{}); +/// try vec2.field("x", 1.5, .{}); +/// try vec2.fieldPrefix(); +/// try serializer.value(2.5); +/// try vec2.finish(); +/// ``` +pub fn Stringifier(comptime Writer: type) type { + return struct { + const Self = @This(); + + pub const MaxDepthError = error{MaxDepth} || Writer.Error; + + options: StringifierOptions, + indent_level: u8, + writer: Writer, + + /// Initialize a stringifier. + fn init(writer: Writer, options: StringifierOptions) Self { + return .{ + .options = options, + .writer = writer, + .indent_level = 0, + }; + } + + /// Serialize a value, similar to `stringify`. + pub fn value(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + return self.valueArbitraryDepth(val, options); + } + + /// Serialize a value, similar to `stringifyMaxDepth`. + pub fn valueMaxDepth(self: *Self, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + try checkValueDepth(val, depth); + return self.valueArbitraryDepth(val, options); + } + + /// Serialize a value, similar to `stringifyArbitraryDepth`. + pub fn valueArbitraryDepth(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .Int => |Int| if (options.emit_utf8_codepoints and + Int.signedness == .unsigned and + Int.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .ComptimeInt => if (options.emit_utf8_codepoints and + val > 0 and + val <= std.math.maxInt(u21) and + std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .Float, .ComptimeFloat => try self.float(val), + .Bool, .Null => try std.fmt.format(self.writer, "{}", .{val}), + .EnumLiteral => { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + }, + .Enum => |Enum| if (Enum.is_exhaustive) { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + } else { + @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums"); + }, + .Void => try self.writer.writeAll("{}"), + .Pointer => |Pointer| { + const child_type = switch (@typeInfo(Pointer.child)) { + .Array => |Array| Array.child, + else => if (Pointer.size != .Slice) @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type") else Pointer.child, + }; + if (child_type == u8 and !options.emit_strings_as_containers) { + try self.string(val); + } else { + try self.sliceImpl(val, options); + } + }, + .Array => { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .Struct => |StructInfo| if (StructInfo.is_tuple) { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = StructInfo.fields.len } }); + inline for (val) |field_value| { + try container.fieldArbitraryDepth(field_value, options); + } + try container.finish(); + } else { + // Decide which fields to emit + const fields, const skipped = if (options.emit_default_optional_fields) b: { + break :b .{ StructInfo.fields.len, [1]bool{false} ** StructInfo.fields.len }; + } else b: { + var fields = StructInfo.fields.len; + var skipped = [1]bool{false} ** StructInfo.fields.len; + inline for (StructInfo.fields, &skipped) |field_info, *skip| { + if (field_info.default_value) |default_field_value_opaque| { + const field_value = @field(val, field_info.name); + const default_field_value: *const @TypeOf(field_value) = @ptrCast(@alignCast(default_field_value_opaque)); + if (std.meta.eql(field_value, default_field_value.*)) { + skip.* = true; + fields -= 1; + } + } + } + break :b .{ fields, skipped }; + }; + + // Emit those fields + var container = try self.startStruct(.{ .whitespace_style = .{ .fields = fields } }); + inline for (StructInfo.fields, skipped) |field_info, skip| { + if (!skip) { + try container.fieldArbitraryDepth(field_info.name, @field(val, field_info.name), options); + } + } + try container.finish(); + }, + .Union => |Union| if (Union.tag_type == null) { + @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); + } else { + var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + switch (val) { + inline else => |pl, tag| try container.fieldArbitraryDepth(@tagName(tag), pl, options), + } + try container.finish(); + }, + .Optional => if (val) |inner| { + try self.valueArbitraryDepth(inner, options); + } else { + try self.writer.writeAll("null"); + }, + + else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), + } + } + + /// Serialize an integer. + pub fn int(self: *Self, val: anytype) Writer.Error!void { + try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); + } + + /// Serialize a float. + pub fn float(self: *Self, val: anytype) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .Float, .ComptimeFloat => if (std.math.isNan(val)) { + return self.writer.writeAll("nan"); + } else if (@as(f128, val) == std.math.inf(f128)) { + return self.writer.writeAll("inf"); + } else if (@as(f128, val) == -std.math.inf(f128)) { + return self.writer.writeAll("-inf"); + } else { + try std.fmt.format(self.writer, "{d}", .{val}); + }, + else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), + } + } + + fn identNeedsEscape(name: []const u8) bool { + std.debug.assert(name.len != 0); + for (name, 0..) |c, i| { + switch (c) { + 'A'...'Z', 'a'...'z', '_' => {}, + '0'...'9' => if (i == 0) return true, + else => return true, + } + } + return std.zig.Token.keywords.has(name); + } + + /// Serialize `name` as an identifier. + /// + /// Escapes the identifier if necessary. + pub fn ident(self: *Self, name: []const u8) Writer.Error!void { + if (identNeedsEscape(name)) { + try self.writer.writeAll("@\""); + try self.writer.writeAll(name); + try self.writer.writeByte('"'); + } else { + try self.writer.writeAll(name); + } + } + + /// Serialize `val` as a UTF8 codepoint. + /// + /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. + pub fn utf8Codepoint(self: *Self, val: u21) (Writer.Error || error{InvalidCodepoint})!void { + var buf: [8]u8 = undefined; + const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; + const str = buf[0..len]; + try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); + } + + /// Like `value`, but always serializes `val` as a slice. + /// + /// Will fail at comptime if `val` is not an array or slice. + pub fn slice(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.sliceArbitraryDepth(val, options); + } + + /// Like `value`, but recursive types are allowed. + /// + /// Returns `error.MaxDepthError` if `depth` is exceeded. + pub fn sliceMaxDepth(self: *Self, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + try checkValueDepth(val, depth); + try self.sliceArbitraryDepth(val, options); + } + + /// Like `value`, but recursive types are allowed. + /// + /// It is the caller's responsibility to ensure that `val` does not contain cycles. + pub fn sliceArbitraryDepth(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + try self.sliceImpl(val, options); + } + + fn sliceImpl(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.itemArbitraryDepth(item_val, options); + } + try container.finish(); + } + + /// Like `value`, but always serializes `val` as a string. + pub fn string(self: *Self, val: []const u8) Writer.Error!void { + try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)}); + } + + /// Options for formatting multiline strings. + pub const MultilineStringOptions = struct { + /// If top level is true, whitespace before and after the multiline string is elided. + /// If it is true, a newline is printed, then the value, followed by a newline, and if + /// whitespace is true any necessary indentation follows. + top_level: bool = false, + }; + + /// Like `value`, but always serializes to a multiline string literal. + /// + /// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline, + /// since multiline strings cannot represent CR without a following newline. + pub fn multilineString(self: *Self, val: []const u8, options: MultilineStringOptions) (Writer.Error || error{InnerCarriageReturn})!void { + // Make sure the string does not contain any carriage returns not followed by a newline + var i: usize = 0; + while (i < val.len) : (i += 1) { + if (val[i] == '\r') { + if (i + 1 < val.len) { + if (val[i + 1] == '\n') { + i += 1; + continue; + } + } + return error.InnerCarriageReturn; + } + } + + if (!options.top_level) { + try self.newline(); + try self.indent(); + } + + try self.writer.writeAll("\\\\"); + for (val) |c| { + if (c != '\r') { + try self.writer.writeByte(c); // We write newlines here even if whitespace off + if (c == '\n') { + try self.indent(); + try self.writer.writeAll("\\\\"); + } + } + } + + if (!options.top_level) { + try self.writer.writeByte('\n'); // Even if whitespace off + try self.indent(); + } + } + + /// Create a `Struct` for writing ZON structs field by field. + pub fn startStruct(self: *Self, options: StringifyContainerOptions) Writer.Error!Struct { + return Struct.start(self, options); + } + + /// Creates a `Tuple` for writing ZON tuples field by field. + pub fn startTuple(self: *Self, options: StringifyContainerOptions) Writer.Error!Tuple { + return Tuple.start(self, options); + } + + /// Creates a `Slice` for writing ZON slices item by item. + pub fn startSlice(self: *Self, options: StringifyContainerOptions) Writer.Error!Slice { + return Slice.start(self, options); + } + + fn indent(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByteNTimes(' ', 4 * self.indent_level); + } + } + + fn newline(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte('\n'); + } + } + + fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void { + if (self.containerShouldWrap(len)) { + try self.newline(); + } else { + try self.space(); + } + } + + fn space(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte(' '); + } + } + + /// Writes ZON tuples field by field. + pub const Tuple = struct { + container: Container, + + fn start(parent: *Self, options: StringifyContainerOptions) 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: StringifyValueOptions) 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: StringifyValueOptions, depth: usize) MaxDepthError!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: StringifyValueOptions) 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: StringifyContainerOptions) 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: StringifyValueOptions) 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: StringifyValueOptions, depth: usize) MaxDepthError!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: StringifyValueOptions) Writer.Error!void { + try self.container.fieldArbitraryDepth(name, val, options); + } + + /// Print a field prefix. This prints any necessary commas, the field name (escaped if + /// necessary) and whitespace as configured. Useful if you want to serialize the field + /// value yourself. + pub fn fieldPrefix(self: *Struct, name: []const u8) Writer.Error!void { + try self.container.fieldPrefix(name); + } + }; + + /// Writes ZON slices field by field. + pub const Slice = struct { + container: Container, + + fn start(parent: *Self, options: StringifyContainerOptions) Writer.Error!Slice { + try parent.writer.writeByte('&'); + return .{ + .container = try Container.start(parent, .anon, options), + }; + } + + /// Finishes serializing the slice. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Slice) Writer.Error!void { + try self.container.finish(); + self.* = undefined; + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. + pub fn item(self: *Slice, val: anytype, options: StringifyValueOptions) Writer.Error!void { + try self.container.field(null, val, options); + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. + pub fn itemMaxDepth(self: *Slice, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + try self.container.fieldMaxDepth(null, val, options, depth); + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueArbitraryDepth`. + pub fn itemArbitraryDepth(self: *Slice, val: anytype, options: StringifyValueOptions) Writer.Error!void { + try self.container.fieldArbitraryDepth(null, val, options); + } + + /// Print a field prefix. This prints any necessary commas, and whitespace as + /// configured. Useful if you want to serialize the item value yourself. + pub fn itemPrefix(self: *Slice) Writer.Error!void { + try self.container.fieldPrefix(null); + } + }; + + const Container = struct { + const FieldStyle = enum { named, anon }; + + serializer: *Self, + field_style: FieldStyle, + options: StringifyContainerOptions, + empty: bool, + + fn start(serializer: *Self, field_style: FieldStyle, options: StringifyContainerOptions) Writer.Error!Container { + if (options.shouldWrap()) serializer.indent_level +|= 1; + try serializer.writer.writeAll(".{"); + return .{ + .serializer = serializer, + .field_style = field_style, + .options = options, + .empty = true, + }; + } + + fn finish(self: *Container) Writer.Error!void { + if (self.options.shouldWrap()) self.serializer.indent_level -|= 1; + if (!self.empty) { + if (self.options.shouldWrap()) { + if (self.serializer.options.whitespace) { + try self.serializer.writer.writeByte(','); + } + try self.serializer.newline(); + try self.serializer.indent(); + } else if (!self.shouldElideSpaces()) { + try self.serializer.space(); + } + } + try self.serializer.writer.writeByte('}'); + self.* = undefined; + } + + fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void { + if (!self.empty) { + try self.serializer.writer.writeByte(','); + } + self.empty = false; + if (self.options.shouldWrap()) { + try self.serializer.newline(); + } else if (!self.shouldElideSpaces()) { + try self.serializer.space(); + } + if (self.options.shouldWrap()) try self.serializer.indent(); + if (name) |n| { + try self.serializer.writer.writeByte('.'); + try self.serializer.ident(n); + try self.serializer.space(); + try self.serializer.writer.writeByte('='); + try self.serializer.space(); + } + } + + fn field(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.fieldArbitraryDepth(name, val, options); + } + + fn fieldMaxDepth(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + try checkValueDepth(val, depth); + try self.fieldArbitraryDepth(name, val, options); + } + + fn fieldArbitraryDepth(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions) Writer.Error!void { + try self.fieldPrefix(name); + try self.serializer.valueArbitraryDepth(val, options); + } + + fn shouldElideSpaces(self: *const Container) bool { + return switch (self.options.whitespace_style) { + .fields => |fields| self.field_style != .named and fields == 1, + else => false, + }; + } + }; + + fn comptimeAssertNoRecursion(comptime T: type) void { + if (comptime typeIsRecursive(T)) { + @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); + } + } + }; +} + +/// Creates an instance of `Stringifier`. +pub fn stringifier(writer: anytype, options: StringifierOptions) Stringifier(@TypeOf(writer)) { + return Stringifier(@TypeOf(writer)).init(writer, options); +} + +fn expectStringifyEqual(expected: []const u8, value: anytype, comptime options: StringifyOptions) !void { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + try stringify(value, options, buf.writer()); + try std.testing.expectEqualStrings(expected, buf.items); +} + +test "std.zon stringify whitespace, high level API" { + try expectStringifyEqual(".{}", .{}, .{}); + try expectStringifyEqual(".{}", .{}, .{ .whitespace = false }); + + try expectStringifyEqual(".{1}", .{1}, .{}); + try expectStringifyEqual(".{1}", .{1}, .{ .whitespace = false }); + + try expectStringifyEqual(".{1}", @as([1]u32, .{1}), .{}); + try expectStringifyEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false }); + + try expectStringifyEqual("&.{1}", @as([]const u32, &.{1}), .{}); + try expectStringifyEqual("&.{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); + + try expectStringifyEqual(".{ .x = 1 }", .{ .x = 1 }, .{}); + try expectStringifyEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false }); + + try expectStringifyEqual(".{ 1, 2 }", .{ 1, 2 }, .{}); + try expectStringifyEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false }); + + try expectStringifyEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{}); + try expectStringifyEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false }); + + try expectStringifyEqual("&.{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{}); + try expectStringifyEqual("&.{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false }); + + try expectStringifyEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{}); + try expectStringifyEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false }); + + try expectStringifyEqual( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , .{ 1, 2, 3 }, .{}); + try expectStringifyEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false }); + + try expectStringifyEqual( + \\.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , @as([3]u32, .{ 1, 2, 3 }), .{}); + try expectStringifyEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false }); + + try expectStringifyEqual( + \\&.{ + \\ 1, + \\ 2, + \\ 3, + \\} + , @as([]const u32, &.{ 1, 2, 3 }), .{}); + try expectStringifyEqual("&.{1,2,3}", @as([]const u32, &.{ 1, 2, 3 }), .{ .whitespace = false }); + + try expectStringifyEqual( + \\.{ + \\ .x = 1, + \\ .y = 2, + \\ .z = 3, + \\} + , .{ .x = 1, .y = 2, .z = 3 }, .{}); + try expectStringifyEqual(".{.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 expectStringifyEqual(".{ .b = 1 }", Union{ .b = 1 }, .{}); + try expectStringifyEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false }); + + // Nested indentation where outer object doesn't wrap + try expectStringifyEqual( + \\.{ .inner = .{ + \\ 1, + \\ 2, + \\ 3, + \\} } + , .{ .inner = .{ 1, 2, 3 } }, .{}); +} + +test "std.zon stringify whitespace, low level API" { + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + const writer = buffer.writer(); + var serializer = stringifier(writer, .{}); + + inline for (.{ true, false }) |whitespace| { + serializer.options = .{ .whitespace = whitespace }; + + // Empty containers + { + var container = try serializer.startStruct(.{}); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{}); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 0 } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 0 } }); + try container.finish(); + try std.testing.expectEqualStrings(".{}", buffer.items); + buffer.clearRetainingCapacity(); + } + + // Size 1 + { + var container = try serializer.startStruct(.{}); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{}); + try container.field(1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{1}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + // We get extra spaces here, since we didn't know up front that there would only be one + // field. + var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.field(1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{1}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + try container.field("a", 1, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ .a = 1 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 1 } }); + try container.field(1, .{}); + try container.finish(); + try std.testing.expectEqualStrings(".{1}", buffer.items); + buffer.clearRetainingCapacity(); + } + + // Size 2 + { + var container = try serializer.startStruct(.{}); + try container.field("a", 1, .{}); + try container.field("b", 2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ .a = 1, + \\ .b = 2, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{}); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings( + \\.{ + \\ 1, + \\ 2, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1, 2 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 2 } }); + try container.field(1, .{}); + try container.field(2, .{}); + try container.finish(); + if (whitespace) { + try std.testing.expectEqualStrings(".{ 1, 2 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + // Size 3 + { + var container = try serializer.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, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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 }", buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + { + var container = try serializer.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, + \\} + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + + // Nested objects where the outer container doesn't wrap but the inner containers do + { + var container = try serializer.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, + \\} } + , buffer.items); + } else { + try std.testing.expectEqualStrings(".{.first=.{1,2,3},.second=.{4,5,6}}", buffer.items); + } + buffer.clearRetainingCapacity(); + } + } +} + +test "std.zon stringify utf8 codepoints" { + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + const writer = buffer.writer(); + var serializer = stringifier(writer, .{}); + + // Minimal case + try serializer.utf8Codepoint('a'); + try std.testing.expectEqualStrings("'a'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.int('a'); + try std.testing.expectEqualStrings("97", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('a', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'a'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('a', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buffer.items); + buffer.clearRetainingCapacity(); + + // Short escaped codepoint + try serializer.utf8Codepoint('\n'); + try std.testing.expectEqualStrings("'\\n'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.int('\n'); + try std.testing.expectEqualStrings("10", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('\n', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\n'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('\n', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("10", buffer.items); + buffer.clearRetainingCapacity(); + + // Large codepoint + try serializer.utf8Codepoint('⚡'); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.int('⚡'); + try std.testing.expectEqualStrings("9889", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('⚡', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value('⚡', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("9889", buffer.items); + buffer.clearRetainingCapacity(); + + // Invalid codepoint + try std.testing.expectError(error.InvalidCodepoint, serializer.utf8Codepoint(0x110000 + 1)); + + try serializer.int(0x110000 + 1); + try std.testing.expectEqualStrings("1114113", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value(0x110000 + 1, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("1114113", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value(0x110000 + 1, .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("1114113", buffer.items); + buffer.clearRetainingCapacity(); + + // Valid codepoint, not a codepoint type + try serializer.value(@as(u22, 'a'), .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("97", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value(@as(i32, 'a'), .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buffer.items); + buffer.clearRetainingCapacity(); + + // Make sure value options are passed to children + try serializer.value(.{ .c = '⚡' }, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value(.{ .c = '⚡' }, .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings(".{ .c = 9889 }", buffer.items); + buffer.clearRetainingCapacity(); +} + +test "std.zon stringify strings" { + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + const writer = buffer.writer(); + var serializer = stringifier(writer, .{}); + + // Minimal case + try serializer.string("abc⚡\n"); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.slice("abc⚡\n", .{}); + try std.testing.expectEqualStrings( + \\&.{ + \\ 97, + \\ 98, + \\ 99, + \\ 226, + \\ 154, + \\ 161, + \\ 10, + \\} + , buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value("abc⚡\n", .{}); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value("abc⚡\n", .{ .emit_strings_as_containers = true }); + try std.testing.expectEqualStrings( + \\&.{ + \\ 97, + \\ 98, + \\ 99, + \\ 226, + \\ 154, + \\ 161, + \\ 10, + \\} + , buffer.items); + buffer.clearRetainingCapacity(); + + // Value options are inherited by children + try serializer.value(.{ .str = "abc" }, .{}); + try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); + try std.testing.expectEqualStrings( + \\.{ .str = &.{ + \\ 97, + \\ 98, + \\ 99, + \\} } + , buffer.items); + buffer.clearRetainingCapacity(); + + // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can round trip + // correctly. + try serializer.value("abc".*, .{}); + try std.testing.expectEqualStrings( + \\.{ + \\ 97, + \\ 98, + \\ 99, + \\} + , buffer.items); + buffer.clearRetainingCapacity(); +} + +test "std.zon stringify multiline strings" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + const writer = buf.writer(); + var serializer = stringifier(writer, .{}); + + inline for (.{ true, false }) |whitespace| { + serializer.options.whitespace = whitespace; + + { + try serializer.multilineString("", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.multilineString("abc⚡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abc⚡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.multilineString("abc⚡\ndef", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.multilineString("abc⚡\r\ndef", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\abc⚡\n\\\\def", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.multilineString("\nabc⚡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.multilineString("\r\nabc⚡", .{ .top_level = true }); + try std.testing.expectEqualStrings("\\\\\n\\\\abc⚡", buf.items); + buf.clearRetainingCapacity(); + } + + { + try serializer.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 serializer.string(str); + try std.testing.expectEqualStrings("\"a\\rc\"", buf.items); + buf.clearRetainingCapacity(); + } + + { + try std.testing.expectError(error.InnerCarriageReturn, serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{})); + try std.testing.expectError(error.InnerCarriageReturn, serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{})); + try std.testing.expectError(error.InnerCarriageReturn, serializer.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 expectStringifyEqual( + \\.{ + \\ .x = 2, + \\ .y = 3, + \\ .z = 4, + \\ .inner1 = .{ + \\ .a = '1', + \\ .b = '2', + \\ .c = '3', + \\ }, + \\ .inner2 = .{ + \\ 'a', + \\ 'b', + \\ 'c', + \\ }, + \\ .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\ }, + \\} + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = '1', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ .emit_utf8_codepoints = true }, + ); + + // Top level defaults + try expectStringifyEqual( + \\.{ .y = 3, .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\} } + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = '1', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ + .emit_default_optional_fields = false, + .emit_utf8_codepoints = true, + }, + ); + + // Inner types having defaults, and defaults changing the number of fields affecting the formatting + try expectStringifyEqual( + \\.{ + \\ .y = 3, + \\ .inner1 = .{ .b = '2', .c = '3' }, + \\ .inner3 = .{ + \\ 'a', + \\ 'b', + \\ 'd', + \\ }, + \\} + , + Struct{ + .y = 3, + .z = 4, + .inner1 = .{ + .a = 'z', + .b = '2', + .c = '3', + }, + .inner3 = .{ + 'a', + 'b', + 'd', + }, + }, + .{ + .emit_default_optional_fields = false, + .emit_utf8_codepoints = true, + }, + ); + + const DefaultStrings = struct { + foo: []const u8 = "abc", + }; + try expectStringifyEqual( + \\.{} + , + DefaultStrings{ .foo = "abc" }, + .{ .emit_default_optional_fields = false }, + ); + try expectStringifyEqual( + \\.{ .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 stringifyMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); + try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); + buf.clearRetainingCapacity(); + + try stringifyArbitraryDepth(.{ 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.MaxDepth, stringifyMaxDepth(.{ 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 stringifyMaxDepth(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 stringifyArbitraryDepth(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.MaxDepth, stringifyMaxDepth(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.MaxDepth, stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 2)); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + var serializer = stringifier(buf.writer(), .{}); + + try std.testing.expectError(error.MaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 2)); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + try serializer.sliceArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + buf.clearRetainingCapacity(); + } + + // A slice succeeding + { + var temp: [1]Recurse = .{.{ .r = &.{} }}; + const maybe_recurse: []const Recurse = &temp; + + try stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 3); + try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + buf.clearRetainingCapacity(); + + var serializer = stringifier(buf.writer(), .{}); + + try serializer.sliceMaxDepth(maybe_recurse, .{}, 3); + try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + buf.clearRetainingCapacity(); + + try serializer.sliceArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + buf.clearRetainingCapacity(); + } + + // Max depth failing on recursive type due to recursion + { + var temp: [1]Recurse = .{.{ .r = &.{} }}; + temp[0].r = &temp; + const maybe_recurse: []const Recurse = &temp; + + try std.testing.expectError(error.MaxDepth, stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 128)); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + + var serializer = stringifier(buf.writer(), .{}); + try std.testing.expectError(error.MaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 128)); + try std.testing.expectEqualStrings("", buf.items); + buf.clearRetainingCapacity(); + } + + // Max depth on other parts of the lower level API + { + const writer = buf.writer(); + var serializer = stringifier(writer, .{}); + + const maybe_recurse: []const Recurse = &.{}; + + try std.testing.expectError(error.MaxDepth, serializer.valueMaxDepth(1, .{}, 0)); + try serializer.valueMaxDepth(2, .{}, 1); + try serializer.value(3, .{}); + try serializer.valueArbitraryDepth(maybe_recurse, .{}); + + var s = try serializer.startStruct(.{}); + try std.testing.expectError(error.MaxDepth, 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 serializer.startTuple(.{}); + try std.testing.expectError(error.MaxDepth, t.fieldMaxDepth(1, .{}, 0)); + try t.fieldMaxDepth(6, .{}, 1); + try t.field(7, .{}); + try t.fieldArbitraryDepth(maybe_recurse, .{}); + try t.finish(); + + var a = try serializer.startSlice(.{}); + try std.testing.expectError(error.MaxDepth, a.itemMaxDepth(1, .{}, 0)); + try a.itemMaxDepth(8, .{}, 1); + try a.item(9, .{}); + try a.itemArbitraryDepth(maybe_recurse, .{}); + try a.finish(); + + try std.testing.expectEqualStrings( + \\23&.{}.{ + \\ .b = 4, + \\ .c = 5, + \\ .d = &.{}, + \\}.{ + \\ 6, + \\ 7, + \\ &.{}, + \\}&.{ + \\ 8, + \\ 9, + \\ &.{}, + \\} + , buf.items); + } +} + +test "std.zon stringify primitives" { + // Issue: https://github.com/ziglang/zig/issues/20880 + if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; + + try expectStringifyEqual( + \\.{ + \\ .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 expectStringifyEqual( + \\.{ + \\ .a = 18446744073709551616, + \\ .b = -18446744073709551616, + \\ .c = 680564733841876926926749214863536422912, + \\ .d = -680564733841876926926749214863536422912, + \\ .e = 0, + \\} + , + .{ + .a = 18446744073709551616, + .b = -18446744073709551616, + .c = 680564733841876926926749214863536422912, + .d = -680564733841876926926749214863536422912, + .e = 0, + }, + .{}, + ); + + try expectStringifyEqual( + \\.{ + \\ .a = true, + \\ .b = false, + \\ .c = .foo, + \\ .d = {}, + \\ .e = null, + \\} + , + .{ + .a = true, + .b = false, + .c = .foo, + .d = {}, + .e = null, + }, + .{}, + ); + + const Struct = struct { x: f32, y: f32 }; + try expectStringifyEqual( + ".{ .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 expectStringifyEqual( + ".{ .a = .foo, .b = .foo }", + .{ + .a = .foo, + .b = E.foo, + }, + .{}, + ); +} + +test "std.zon stringify ident" { + var buffer = std.ArrayList(u8).init(std.testing.allocator); + defer buffer.deinit(); + const writer = buffer.writer(); + var serializer = stringifier(writer, .{}); + + try serializer.ident("a"); + try std.testing.expectEqualStrings("a", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("foo_1"); + try std.testing.expectEqualStrings("foo_1", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("_foo_1"); + try std.testing.expectEqualStrings("_foo_1", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("foo bar"); + try std.testing.expectEqualStrings("@\"foo bar\"", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("1foo"); + try std.testing.expectEqualStrings("@\"1foo\"", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("var"); + try std.testing.expectEqualStrings("@\"var\"", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("true"); + try std.testing.expectEqualStrings("true", buffer.items); + buffer.clearRetainingCapacity(); + + try serializer.ident("_"); + try std.testing.expectEqualStrings("_", buffer.items); + buffer.clearRetainingCapacity(); + + const Enum = enum { + @"foo bar", + }; + try expectStringifyEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{ + .@"var" = .@"foo bar", + .@"1" = Enum.@"foo bar", + }, .{}); +} diff --git a/src/Air.zig b/src/Air.zig index 4589bb1557cf..883991488c7c 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1020,6 +1020,11 @@ pub const Inst = struct { pub fn toType(ref: Ref) Type { return Type.fromInterned(ref.toInterned().?); } + + pub fn toTypeAllowNone(ref: Ref) ?Type { + if (ref == .none) return null; + return ref.toType(); + } }; /// All instructions have an 8-byte payload, which is contained within diff --git a/src/Compilation.zig b/src/Compilation.zig index 241386f10c57..ddac17139a73 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2215,7 +2215,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.mode == .zig) { + comp.astgen_work_queue.writeItemAssumeCapacity(file_index); + } } if (comp.file_system_inputs) |fsi| { for (zcu.import_table.values()) |file_index| { @@ -4240,6 +4243,7 @@ fn workerAstGenFile( wg: *WaitGroup, src: Zcu.AstGenSrc, ) void { + assert(file.mode == .zig); const child_prog_node = prog_node.start(file.sub_file_path, 0); defer child_prog_node.end(); @@ -4293,7 +4297,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.mode == .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/Package/Manifest.zig b/src/Package/Manifest.zig index 4eed6cc386e6..7691ac3405e1 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -14,6 +14,7 @@ pub const Digest = [Hash.digest_length]u8; pub const multihash_len = 1 + 1 + Hash.digest_length; pub const multihash_hex_digest_len = 2 * multihash_len; pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; +const AstGen = std.zig.AstGen; pub const Dependency = struct { location: Location, @@ -456,7 +457,6 @@ const Parse = struct { return duped; } - /// TODO: try to DRY this with AstGen.parseStrLit fn parseStrLit( p: *Parse, token: Ast.TokenIndex, @@ -470,95 +470,13 @@ const Parse = struct { buf.* = buf_managed.moveToUnmanaged(); switch (try result) { .success => {}, - .failure => |err| try p.appendStrLitError(err, token, bytes, offset), - } - } - - /// TODO: try to DRY this with AstGen.failWithStrLitError - fn appendStrLitError( - p: *Parse, - err: std.zig.string_literal.Error, - token: Ast.TokenIndex, - bytes: []const u8, - offset: u32, - ) Allocator.Error!void { - const raw_string = bytes[offset..]; - switch (err) { - .invalid_escape_character => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "invalid escape character: '{c}'", - .{raw_string[bad_index]}, - ); - }, - .expected_hex_digit => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected hex digit, found '{c}'", - .{raw_string[bad_index]}, - ); - }, - .empty_unicode_escape_sequence => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "empty unicode escape sequence", - .{}, - ); - }, - .expected_hex_digit_or_rbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected hex digit or '}}', found '{c}'", - .{raw_string[bad_index]}, - ); - }, - .invalid_unicode_codepoint => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "unicode escape does not correspond to a valid unicode scalar value", - .{}, - ); - }, - .expected_lbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected '{{', found '{c}", - .{raw_string[bad_index]}, - ); - }, - .expected_rbrace => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected '}}', found '{c}", - .{raw_string[bad_index]}, - ); - }, - .expected_single_quote => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "expected single quote ('), found '{c}", - .{raw_string[bad_index]}, - ); - }, - .invalid_character => |bad_index| { - try p.appendErrorOff( - token, - offset + @as(u32, @intCast(bad_index)), - "invalid byte in string or character literal: '{c}'", - .{raw_string[bad_index]}, - ); - }, - .empty_char_literal => { - try p.appendErrorOff(token, offset, "empty character literal", .{}); - }, + .failure => |err| try appendErrorOff( + p, + token, + offset + @as(u32, @intCast(err.offset())), + "{}", + err.fmtWithSource(raw_string), + ), } } diff --git a/src/Package/Module.zig b/src/Package/Module.zig index af91b01c9a8e..9d782a00fc53 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -484,6 +484,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .status = .never_loaded, .prev_status = .never_loaded, .mod = new, + .mode = .zig, }; break :b new; }; diff --git a/src/Sema.zig b/src/Sema.zig index 081e51af36c6..58dcb2b851b9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -202,6 +202,7 @@ const Alignment = InternPool.Alignment; const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; const Cache = std.Build.Cache; +const zon = @import("zon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -14415,9 +14416,12 @@ 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); + + _ = extra.res_ty; const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) { error.ImportOutsideModulePath => { @@ -14434,12 +14438,32 @@ 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.mode) { + .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) }); + }; + const res_ty_inst = try sema.resolveInstAllowNone(extra.res_ty); + const res_ty = res_ty_inst.toTypeAllowNone(); + const interned = try zon.lower( + sema, + result.file, + result.file_index, + res_ty, + ); + return Air.internedToRef(interned); + }, + } } fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { diff --git a/src/Zcu.zig b/src/Zcu.zig index 60aeffdf4457..063e4e7e2b6d 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -673,6 +673,9 @@ pub const File = struct { /// successful, this field is unloaded. prev_zir: ?*Zir = null, + /// Whether the file is Zig or ZON. This filed is always populated. + mode: Ast.Mode, + pub const Status = enum { never_loaded, retryable_failure, @@ -692,6 +695,17 @@ pub const File = struct { root: *Package.Module, }; + pub fn modeFromPath(path: []const u8) Ast.Mode { + if (std.mem.endsWith(u8, path, ".zon")) { + return .zon; + } else if (std.mem.endsWith(u8, path, ".zig")) { + return .zig; + } else { + // `Module.importFile` rejects all other extensions + unreachable; + } + } + pub fn unload(file: *File, gpa: Allocator) void { file.unloadTree(gpa); file.unloadSource(gpa); @@ -766,7 +780,7 @@ 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.mode); file.tree_loaded = true; return &file.tree; } @@ -883,6 +897,7 @@ pub const File = struct { pub const Index = InternPool.FileIndex; }; +/// Represents the contents of a file loaded with `@embedFile`. pub const EmbedFile = struct { /// Relative to the owning module's root directory. sub_file_path: InternPool.NullTerminatedString, @@ -2325,6 +2340,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; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index ce79c8ca2117..8c124b31c3eb 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1876,6 +1876,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.mode == .zig); assert(zcu.fileRootType(file_index) == .none); if (file.status != .success_zir) { @@ -2001,6 +2002,7 @@ pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult { .status = .never_loaded, .prev_status = .never_loaded, .mod = mod, + .mode = Zcu.File.modeFromPath(sub_file_path), }; try new_file.addReference(zcu, .{ .root = mod }); @@ -2031,7 +2033,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; @@ -2112,6 +2116,7 @@ pub fn importFile( .status = .never_loaded, .prev_status = .never_loaded, .mod = mod, + .mode = Zcu.File.modeFromPath(sub_file_path), }; return .{ diff --git a/src/main.zig b/src/main.zig index 7bb51bbd8ea9..a13e94123c8a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6106,6 +6106,7 @@ fn cmdAstCheck( .tree = undefined, .zir = undefined, .mod = undefined, + .mode = .zig, }; if (zig_source_file) |file_name| { var f = fs.cwd().openFile(file_name, .{}) catch |err| { @@ -6490,6 +6491,7 @@ fn cmdDumpZir( .tree = undefined, .zir = try Zcu.loadZirCache(gpa, f), .mod = undefined, + .mode = .zig, }; defer file.zir.deinit(gpa); @@ -6562,6 +6564,7 @@ fn cmdChangelist( .tree = undefined, .zir = undefined, .mod = undefined, + .mode = Zcu.File.modeFromPath(old_source_file), }; file.mod = try Package.Module.createLimited(arena, .{ diff --git a/src/print_zir.zig b/src/print_zir.zig index 14502d8ba1f7..d9cc3f546754 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -490,7 +490,6 @@ const Writer = struct { .enum_literal, .decl_ref, .decl_val, - .import, .ret_err_value, .ret_err_value_code, .param_anytype, @@ -517,6 +516,8 @@ const Writer = struct { .declaration => try self.writeDeclaration(stream, inst), .extended => try self.writeExtended(stream, inst), + + .import => try self.writeImport(stream, inst), } } @@ -2862,4 +2863,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/src/zon.zig b/src/zon.zig new file mode 100644 index 000000000000..7502060c0eed --- /dev/null +++ b/src/zon.zig @@ -0,0 +1,661 @@ +const std = @import("std"); +const Zcu = @import("Zcu.zig"); +const Sema = @import("Sema.zig"); +const InternPool = @import("InternPool.zig"); +const Type = @import("Type.zig"); +const Zir = std.zig.Zir; +const AstGen = std.zig.AstGen; +const CompileError = Zcu.CompileError; +const Ast = std.zig.Ast; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const File = Zcu.File; +const LazySrcLoc = Zcu.LazySrcLoc; +const Ref = std.zig.Zir.Inst.Ref; +const NullTerminatedString = InternPool.NullTerminatedString; +const NumberLiteralError = std.zig.number_literal.Error; + +const LowerZon = @This(); + +sema: *Sema, +file: *File, +file_index: Zcu.File.Index, + +/// Lowers the given file as ZON. `res_ty` is a hint that's used to add indirection as needed to +/// match the result type, actual type checking is not done until assignment. +pub fn lower( + sema: *Sema, + file: *File, + file_index: Zcu.File.Index, + res_ty: ?Type, +) CompileError!InternPool.Index { + const lower_zon: LowerZon = .{ + .sema = sema, + .file = file, + .file_index = file_index, + }; + const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated + if (tree.errors.len != 0) { + return lower_zon.lowerAstErrors(); + } + + const data = tree.nodes.items(.data); + const root = data[0].lhs; + return lower_zon.expr(root, res_ty); +} + +fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { + return .{ + .base_node_inst = try self.sema.pt.zcu.intern_pool.trackZir( + self.sema.pt.zcu.gpa, + .main, + .{ .file = self.file_index, .inst = .main_struct_inst }, + ), + .offset = loc, + }; +} + +fn fail( + self: LowerZon, + loc: LazySrcLoc.Offset, + comptime format: []const u8, + args: anytype, +) (Allocator.Error || error{AnalysisFail}) { + @setCold(true); + const src_loc = try self.lazySrcLoc(loc); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); + try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); + return error.AnalysisFail; +} + +fn lowerAstErrors(self: LowerZon) CompileError { + const tree = self.file.tree; + assert(tree.errors.len > 0); + + const gpa = self.sema.gpa; + const ip = &self.sema.pt.zcu.intern_pool; + const parse_err = tree.errors[0]; + + var buf: std.ArrayListUnmanaged(u8) = .{}; + defer buf.deinit(gpa); + + // Create the main error + buf.clearRetainingCapacity(); + try tree.renderError(parse_err, buf.writer(gpa)); + const err_msg = try Zcu.ErrorMsg.create( + gpa, + .{ + .base_node_inst = try ip.trackZir(gpa, .main, .{ + .file = self.file_index, + .inst = .main_struct_inst, + }), + .offset = .{ .token_abs = parse_err.token + @intFromBool(parse_err.token_is_prev) }, + }, + "{s}", + .{buf.items}, + ); + + // Check for invalid bytes + const token_starts = tree.tokens.items(.start); + const token_tags = tree.tokens.items(.tag); + if (token_tags[parse_err.token + @intFromBool(parse_err.token_is_prev)] == .invalid) { + const bad_off: u32 = @intCast(tree.tokenSlice(parse_err.token + @intFromBool(parse_err.token_is_prev)).len); + const byte_abs = token_starts[parse_err.token + @intFromBool(parse_err.token_is_prev)] + bad_off; + try self.sema.pt.zcu.errNote( + .{ + .base_node_inst = try ip.trackZir(gpa, .main, .{ + .file = self.file_index, + .inst = .main_struct_inst, + }), + .offset = .{ .byte_abs = byte_abs }, + }, + err_msg, + "invalid byte: '{'}'", + .{std.zig.fmtEscapes(tree.source[byte_abs..][0..1])}, + ); + } + + // Create the notes + for (tree.errors[1..]) |note| { + if (!note.is_note) break; + + buf.clearRetainingCapacity(); + try tree.renderError(note, buf.writer(gpa)); + try self.sema.pt.zcu.errNote( + .{ + .base_node_inst = try ip.trackZir(gpa, .main, .{ + .file = self.file_index, + .inst = .main_struct_inst, + }), + .offset = .{ .token_abs = note.token + @intFromBool(note.token_is_prev) }, + }, + err_msg, + "{s}", + .{buf.items}, + ); + } + + try self.sema.pt.zcu.failed_files.putNoClobber(gpa, self.file, err_msg); + return error.AnalysisFail; +} + +const Ident = struct { + bytes: []const u8, + owned: bool, + + fn deinit(self: *Ident, allocator: Allocator) void { + if (self.owned) { + allocator.free(self.bytes); + } + self.* = undefined; + } +}; + +fn ident(self: LowerZon, token: Ast.TokenIndex) !Ident { + var bytes = self.file.tree.tokenSlice(token); + + if (bytes[0] == '@' and bytes[1] == '"') { + const gpa = self.sema.gpa; + + const raw_string = bytes[1..bytes.len]; + var parsed = std.ArrayListUnmanaged(u8){}; + defer parsed.deinit(gpa); + + switch (try std.zig.string_literal.parseWrite(parsed.writer(gpa), raw_string)) { + .success => { + if (std.mem.indexOfScalar(u8, parsed.items, 0) != null) { + return self.fail(.{ .token_abs = token }, "identifier cannot contain null bytes", .{}); + } + return .{ + .bytes = try parsed.toOwnedSlice(gpa), + .owned = true, + }; + }, + .failure => |err| { + const offset = self.file.tree.tokens.items(.start)[token]; + return self.fail( + .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, + "{}", + .{err.fmtWithSource(raw_string)}, + ); + }, + } + } + + return .{ + .bytes = bytes, + .owned = false, + }; +} + +fn identAsNullTerminatedString(self: LowerZon, token: Ast.TokenIndex) !NullTerminatedString { + var parsed = try self.ident(token); + defer parsed.deinit(self.sema.gpa); + const ip = &self.sema.pt.zcu.intern_pool; + return ip.getOrPutString(self.sema.gpa, self.sema.pt.tid, parsed.bytes, .no_embedded_nulls); +} + +const FieldTypes = union(enum) { + st: struct { + ty: Type, + loaded: InternPool.LoadedStructType, + }, + un: struct { + ty: Type, + loaded: InternPool.LoadedEnumType, + }, + none, + + fn init(ty: ?Type, sema: *Sema) !@This() { + const t = ty orelse return .none; + const ip = &sema.pt.zcu.intern_pool; + switch (t.zigTypeTagOrPoison(sema.pt.zcu) catch return .none) { + .Struct => { + try t.resolveFully(sema.pt); + const loaded_struct_type = ip.loadStructType(t.toIntern()); + return .{ .st = .{ + .ty = t, + .loaded = loaded_struct_type, + } }; + }, + .Union => { + try t.resolveFully(sema.pt); + const loaded_union_type = ip.loadUnionType(t.toIntern()); + const loaded_tag_type = loaded_union_type.loadTagType(ip); + return .{ .un = .{ + .ty = t, + .loaded = loaded_tag_type, + } }; + }, + else => return .none, + } + } + + fn get(self: *const @This(), name: NullTerminatedString, zcu: *Zcu) ?Type { + const ip = &zcu.intern_pool; + const self_ty, const index = switch (self.*) { + .st => |st| .{ st.ty, st.loaded.nameIndex(ip, name) orelse return null }, + .un => |un| .{ un.ty, un.loaded.nameIndex(ip, name) orelse return null }, + .none => return null, + }; + return self_ty.structFieldType(index, zcu); + } +}; + +fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { + const gpa = self.sema.gpa; + const ip = &self.sema.pt.zcu.intern_pool; + const data = self.file.tree.nodes.items(.data); + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + + // If the result type is slice, and our AST Node is not a slice, recurse and then take the + // address of the result so attempt to coerce it into a slice. + if (res_ty) |rt| { + const result_is_slice = rt.isSlice(self.sema.pt.zcu); + const ast_is_pointer = switch (tags[node]) { + .string_literal, .multiline_string_literal => true, + else => false, + }; + if (result_is_slice and !ast_is_pointer) { + const val = try self.expr(node, rt.childType(self.sema.pt.zcu)); + const val_type = ip.typeOf(val); + const ptr_type = try self.sema.pt.ptrTypeSema(.{ + .child = val_type, + .flags = .{ + .alignment = .none, + .is_const = true, + .address_space = .generic, + }, + }); + return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ + .ty = ptr_type.toIntern(), + .base_addr = .{ .anon_decl = .{ + .orig_ty = ptr_type.toIntern(), + .val = val, + } }, + .byte_offset = 0, + } }); + } + } + + switch (tags[node]) { + .identifier => { + const token = main_tokens[node]; + var litIdent = try self.ident(token); + defer litIdent.deinit(gpa); + + const LitIdent = enum { true, false, null, nan, inf }; + const values = std.StaticStringMap(LitIdent).initComptime(.{ + .{ "true", .true }, + .{ "false", .false }, + .{ "null", .null }, + .{ "nan", .nan }, + .{ "inf", .inf }, + }); + if (values.get(litIdent.bytes)) |value| { + return switch (value) { + .true => .bool_true, + .false => .bool_false, + .null => .null_value, + .nan => self.sema.pt.intern(.{ .float = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), + .storage = .{ .f128 = std.math.nan(f128) }, + } }), + .inf => try self.sema.pt.intern(.{ .float = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), + .storage = .{ .f128 = std.math.inf(f128) }, + } }), + }; + } + return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{litIdent.bytes}); + }, + .number_literal, .char_literal => return self.number(node, null), + .negation => return self.number(data[node].lhs, node), + .enum_literal => return ip.get(gpa, self.sema.pt.tid, .{ + .enum_literal = try self.identAsNullTerminatedString(main_tokens[node]), + }), + .string_literal => { + const token = main_tokens[node]; + const raw_string = self.file.tree.tokenSlice(token); + + var bytes = std.ArrayListUnmanaged(u8){}; + defer bytes.deinit(gpa); + + switch (try std.zig.string_literal.parseWrite(bytes.writer(gpa), raw_string)) { + .success => {}, + .failure => |err| { + const offset = self.file.tree.tokens.items(.start)[token]; + return self.fail( + .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, + "{}", + .{err.fmtWithSource(raw_string)}, + ); + }, + } + + const array_ty = try self.sema.pt.arrayType(.{ + .len = bytes.items.len, + .sentinel = .zero_u8, + .child = .u8_type, + }); + const array_val = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty.toIntern(), + .storage = .{ + .bytes = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls), + }, + } }); + const ptr_ty = try self.sema.pt.ptrTypeSema(.{ + .child = array_ty.toIntern(), + .flags = .{ + .alignment = .none, + .is_const = true, + .address_space = .generic, + }, + }); + return self.sema.pt.intern(.{ .ptr = .{ + .ty = ptr_ty.toIntern(), + .base_addr = .{ .anon_decl = .{ + .val = array_val, + .orig_ty = ptr_ty.toIntern(), + } }, + .byte_offset = 0, + } }); + }, + .multiline_string_literal => { + var bytes = std.ArrayListUnmanaged(u8){}; + defer bytes.deinit(gpa); + + var parser = std.zig.string_literal.multilineParser(bytes.writer(gpa)); + var tok_i = data[node].lhs; + while (tok_i <= data[node].rhs) : (tok_i += 1) { + try parser.line(self.file.tree.tokenSlice(tok_i)); + } + + const array_ty = try self.sema.pt.arrayType(.{ .len = bytes.items.len, .sentinel = .zero_u8, .child = .u8_type }); + const array_val = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty.toIntern(), + .storage = .{ + .bytes = (try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .no_embedded_nulls)).toString(), + }, + } }); + const ptr_ty = try self.sema.pt.ptrTypeSema(.{ + .child = array_ty.toIntern(), + .flags = .{ + .alignment = .none, + .is_const = true, + .address_space = .generic, + }, + }); + return self.sema.pt.intern(.{ .ptr = .{ + .ty = ptr_ty.toIntern(), + .base_addr = .{ .anon_decl = .{ + .val = array_val, + .orig_ty = ptr_ty.toIntern(), + } }, + .byte_offset = 0, + } }); + }, + .struct_init_one, + .struct_init_one_comma, + .struct_init_dot_two, + .struct_init_dot_two_comma, + .struct_init_dot, + .struct_init_dot_comma, + .struct_init, + .struct_init_comma, + => { + var buf: [2]Ast.Node.Index = undefined; + const struct_init = self.file.tree.fullStructInit(&buf, node).?; + if (struct_init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = struct_init.ast.type_expr }, "type expressions not allowed in ZON", .{}); + } + const types = try gpa.alloc(InternPool.Index, struct_init.ast.fields.len); + defer gpa.free(types); + + const values = try gpa.alloc(InternPool.Index, struct_init.ast.fields.len); + defer gpa.free(values); + + var names = std.AutoArrayHashMapUnmanaged(NullTerminatedString, void){}; + defer names.deinit(gpa); + try names.ensureTotalCapacity(gpa, struct_init.ast.fields.len); + + const rt_field_types = try FieldTypes.init(res_ty, self.sema); + for (struct_init.ast.fields, 0..) |field, i| { + const name_token = self.file.tree.firstToken(field) - 2; + const name = try self.identAsNullTerminatedString(name_token); + const gop = names.getOrPutAssumeCapacity(name); + if (gop.found_existing) { + return self.fail(.{ .token_abs = name_token }, "duplicate field", .{}); + } + + const elem_ty = rt_field_types.get(name, self.sema.pt.zcu); + + values[i] = try self.expr(field, elem_ty); + types[i] = ip.typeOf(values[i]); + } + + const struct_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ + .types = types, + .names = names.entries.items(.key), + .values = values, + }); + return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ + .ty = struct_type, + .storage = .{ .elems = values }, + } }); + }, + .array_init_one, + .array_init_one_comma, + .array_init_dot_two, + .array_init_dot_two_comma, + .array_init_dot, + .array_init_dot_comma, + .array_init, + .array_init_comma, + => { + var buf: [2]Ast.Node.Index = undefined; + const array_init = self.file.tree.fullArrayInit(&buf, node).?; + if (array_init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = array_init.ast.type_expr }, "type expressions not allowed in ZON", .{}); + } + const types = try gpa.alloc(InternPool.Index, array_init.ast.elements.len); + defer gpa.free(types); + const values = try gpa.alloc(InternPool.Index, array_init.ast.elements.len); + defer gpa.free(values); + for (array_init.ast.elements, 0..) |elem, i| { + const elem_ty = if (res_ty) |rt| b: { + const type_tag = rt.zigTypeTagOrPoison(self.sema.pt.zcu) catch break :b null; + switch (type_tag) { + .Array => break :b rt.childType(self.sema.pt.zcu), + .Struct => { + try rt.resolveFully(self.sema.pt); + if (i >= rt.structFieldCount(self.sema.pt.zcu)) break :b null; + break :b rt.structFieldType(i, self.sema.pt.zcu); + }, + else => break :b null, + } + } else null; + values[i] = try self.expr(elem, elem_ty); + types[i] = ip.typeOf(values[i]); + } + + const tuple_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ + .types = types, + .names = &.{}, + .values = values, + }); + return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ + .ty = tuple_type, + .storage = .{ .elems = values }, + } }); + }, + .block_two => if (data[node].lhs == 0 and data[node].rhs == 0) { + return .void_value; + } else { + return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); + }, + else => {}, + } + + return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); +} + +fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !InternPool.Index { + const gpa = self.sema.gpa; + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + switch (tags[node]) { + .char_literal => { + const token = main_tokens[node]; + const token_bytes = self.file.tree.tokenSlice(token); + const char = switch (std.zig.string_literal.parseCharLiteral(token_bytes)) { + .success => |char| char, + .failure => |err| { + const offset = self.file.tree.tokens.items(.start)[token]; + return self.fail( + .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, + "{}", + .{err.fmtWithSource(token_bytes)}, + ); + }, + }; + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_int }), + .storage = .{ .i64 = if (is_negative == null) char else -@as(i64, char) }, + } }); + }, + .number_literal => { + const token = main_tokens[node]; + const token_bytes = self.file.tree.tokenSlice(token); + const parsed = std.zig.number_literal.parseNumberLiteral(token_bytes); + switch (parsed) { + .int => |unsigned| { + if (is_negative) |negative_node| { + if (unsigned == 0) { + return self.fail(.{ .node_abs = negative_node }, "integer literal '-0' is ambiguous", .{}); + } + const signed = std.math.negateCast(unsigned) catch { + var result = try std.math.big.int.Managed.initSet(gpa, unsigned); + defer result.deinit(); + result.negate(); + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = .comptime_int_type, + .storage = .{ .big_int = result.toConst() }, + } }); + }; + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = .comptime_int_type, + .storage = .{ .i64 = signed }, + } }); + } else { + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = .comptime_int_type, + .storage = .{ .u64 = unsigned }, + } }); + } + }, + .big_int => |base| { + var big_int = try std.math.big.int.Managed.init(gpa); + defer big_int.deinit(); + + const prefix_offset: usize = if (base == .decimal) 0 else 2; + big_int.setString(@intFromEnum(base), token_bytes[prefix_offset..]) catch |err| switch (err) { + error.InvalidCharacter => unreachable, // caught in `parseNumberLiteral` + error.InvalidBase => unreachable, // we only pass 16, 8, 2, see above + error.OutOfMemory => return error.OutOfMemory, + }; + + assert(big_int.isPositive()); + + if (is_negative != null) big_int.negate(); + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_int }), + .storage = .{ .big_int = big_int.toConst() }, + } }); + }, + .float => { + const unsigned_float = std.fmt.parseFloat(f128, token_bytes) catch unreachable; // Already validated + const float = if (is_negative == null) unsigned_float else -unsigned_float; + return self.sema.pt.intern(.{ .float = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), + .storage = .{ .f128 = float }, + } }); + }, + .failure => |err| return self.failWithNumberError(token, err), + } + }, + .identifier => { + const token = main_tokens[node]; + const bytes = self.file.tree.tokenSlice(token); + const LitIdent = enum { nan, inf }; + const values = std.StaticStringMap(LitIdent).initComptime(.{ + .{ "nan", .nan }, + .{ "inf", .inf }, + }); + if (values.get(bytes)) |value| { + return switch (value) { + .nan => self.sema.pt.intern(.{ .float = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), + .storage = .{ .f128 = std.math.nan(f128) }, + } }), + .inf => try self.sema.pt.intern(.{ .float = .{ + .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), + .storage = .{ + .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128), + }, + } }), + }; + } + return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{bytes}); + }, + else => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), + } +} + +fn createErrorWithOptionalNote( + self: LowerZon, + src_loc: LazySrcLoc, + comptime fmt: []const u8, + args: anytype, + note: ?[]const u8, +) error{OutOfMemory}!*Zcu.ErrorMsg { + const notes = try self.sema.pt.zcu.gpa.alloc(Zcu.ErrorMsg, if (note == null) 0 else 1); + errdefer self.sema.pt.zcu.gpa.free(notes); + if (note) |n| { + notes[0] = try Zcu.ErrorMsg.init( + self.sema.pt.zcu.gpa, + src_loc, + "{s}", + .{n}, + ); + } + + const err_msg = try Zcu.ErrorMsg.create( + self.sema.pt.zcu.gpa, + src_loc, + fmt, + args, + ); + err_msg.*.notes = notes; + return err_msg; +} + +fn failWithNumberError( + self: LowerZon, + token: Ast.TokenIndex, + err: NumberLiteralError, +) (Allocator.Error || error{AnalysisFail}) { + const offset = self.file.tree.tokens.items(.start)[token]; + const src_loc = try self.lazySrcLoc(.{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }); + const token_bytes = self.file.tree.tokenSlice(token); + const err_msg = try self.createErrorWithOptionalNote( + src_loc, + "{}", + .{err.fmtWithSource(token_bytes)}, + err.noteWithSource(token_bytes), + ); + try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); + return error.AnalysisFail; +} diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig new file mode 100644 index 000000000000..9e503f1a67f8 --- /dev/null +++ b/test/behavior/zon.zig @@ -0,0 +1,268 @@ +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 "void" { + try expectEqual({}, @import("zon/void.zon")); +} + +test "bool" { + try expectEqual(true, @import("zon/true.zon")); + try expectEqual(false, @import("zon/false.zon")); +} + +test "optional" { + // Coercion + const some: ?u32 = @import("zon/some.zon"); + const none: ?u32 = @import("zon/none.zon"); + const @"null": @TypeOf(null) = @import("zon/none.zon"); + try expectEqual(some, 10); + try expectEqual(none, null); + try expectEqual(@"null", null); + + // No coercion + try expectEqual(some, @import("zon/some.zon")); + try expectEqual(none, @import("zon/none.zon")); +} + +test "union" { + const Union = union { + x: f32, + y: bool, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + + try expectEqual(union1.x, 1.5); + try expectEqual(union2.y, true); +} + +test "struct" { + try expectEqual(.{}, @import("zon/vec0.zon")); + try expectEqual(.{ .x = 1.5 }, @import("zon/vec1.zon")); + try expectEqual(.{ .x = 1.5, .y = 2 }, @import("zon/vec2.zon")); + try expectEqual(.{ .@"0" = 1.5, .foo = 2 }, @import("zon/escaped_struct.zon")); + try expectEqual(.{}, @import("zon/empty_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); +} + +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" { + try expectEqualDeep(.{ 1.2, true, "hello", 3 }, @import("zon/tuple.zon")); +} + +test "char" { + try expectEqual('a', @import("zon/a.zon")); + try expectEqual('z', @import("zon/z.zon")); + try expectEqual(-'a', @import("zon/a_neg.zon")); +} + +test "arrays" { + try expectEqual([0]u8{}, @import("zon/vec0.zon")); + try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); + try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); +} + +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/slice-1.zon"); + try expectEqualSlices(u8, expected_slice, found_slice); + + const expected_array: [1]u8 = .{1}; + const found_array: [1]u8 = @import("zon/slice-1.zon"); + try expectEqual(expected_array, found_array); + + const T = struct { u8 }; + const expected_tuple: T = .{1}; + const found_tuple: T = @import("zon/slice-1.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" { + // const foo: [3]u8 = "foo".*; + // const bar: []const u8 = &foo; + 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 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, @import("zon/foo.zon")); + try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); +} + +test "int" { + const expected = .{ + // Test various numbers and types + @as(u8, 10), + @as(i16, 24), + @as(i14, -4), + @as(i32, -123), + + // Test limits + @as(i8, 127), + @as(i8, -128), + + // Test characters + @as(u8, 'a'), + @as(u8, 'z'), + + // Test big integers + @as(u65, 36893488147419103231), + @as(u65, 36893488147419103231), + @as(i128, -18446744073709551615), // Only a big int due to negation + @as(i128, -9223372036854775809), // Only a big int due to negation + + // Test big integer limits + @as(i66, 36893488147419103231), + @as(i66, -36893488147419103232), + + // Test parsing whole number floats as integers + @as(i8, -1), + @as(i8, 123), + + // Test non-decimal integers + @as(i16, 0xff), + @as(i16, -0xff), + @as(i16, 0o77), + @as(i16, -0o77), + @as(i16, 0b11), + @as(i16, -0b11), + + // Test non-decimal big integers + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + }; + const actual: @TypeOf(expected) = @import("zon/ints.zon"); + try expectEqual(expected, actual); +} + +test "floats" { + const expected = .{ + // Test decimals + @as(f16, 0.5), + @as(f32, 123.456), + @as(f64, -123.456), + @as(f128, 42.5), + + // Test whole numbers with and without decimals + @as(f16, 5.0), + @as(f16, 5.0), + @as(f32, -102), + @as(f32, -102), + + // Test characters and negated characters + @as(f32, 'a'), + @as(f32, 'z'), + @as(f32, -'z'), + + // Test big integers + @as(f32, 36893488147419103231), + @as(f32, -36893488147419103231), + @as(f128, 0x1ffffffffffffffff), + @as(f32, 0x1ffffffffffffffff), + + // Exponents, underscores + @as(f32, 123.0E+77), + + // Hexadecimal + @as(f32, 0x103.70p-5), + @as(f32, -0x103.70), + @as(f32, 0x1234_5678.9ABC_CDEFp-10), + }; + const actual: @TypeOf(expected) = @import("zon/floats.zon"); + try expectEqual(actual, expected); +} + +test "inf and nan" { + // comptime float + { + const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isNan(actual[1])); + try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); + try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); + } + + // f32 + { + const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isNan(actual[1])); + try expect(std.math.isPositiveInf(actual[2])); + try expect(std.math.isNegativeInf(actual[3])); + } +} 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/a_neg.zon b/test/behavior/zon/a_neg.zon new file mode 100644 index 000000000000..b14b16f3d6e9 --- /dev/null +++ b/test/behavior/zon/a_neg.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/empty_struct.zon b/test/behavior/zon/empty_struct.zon new file mode 100644 index 000000000000..47c47bc057a0 --- /dev/null +++ b/test/behavior/zon/empty_struct.zon @@ -0,0 +1 @@ +.{} 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..c5cb978f3303 --- /dev/null +++ b/test/behavior/zon/escaped_struct.zon @@ -0,0 +1,2 @@ +// zig fmt: off +.{ .@"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..4ea199087977 --- /dev/null +++ b/test/behavior/zon/floats.zon @@ -0,0 +1,26 @@ +.{ + 0.5, + 123.456, + -123.456, + 42.5, + + 5.0, + 5, + -102.0, + -102, + + 'a', + 'z', + -'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..0b264f8ded4d --- /dev/null +++ b/test/behavior/zon/inf_and_nan.zon @@ -0,0 +1,6 @@ +.{ + nan, + -nan, + inf, + -inf, +} diff --git a/test/behavior/zon/ints.zon b/test/behavior/zon/ints.zon new file mode 100644 index 000000000000..fb1060324e2b --- /dev/null +++ b/test/behavior/zon/ints.zon @@ -0,0 +1,40 @@ +.{ + 10, + 24, + -4, + -123, + + 127, + -128, + + 'a', + 'z', + + 36893488147419103231, + 368934_881_474191032_31, + -18446744073709551615, + -9223372036854775809, + + 36893488147419103231, + -36893488147419103232, + + -1.0, + 123.0, + + 0xff, + -0xff, + 0o77, + -0o77, + 0b11, + -0b11, + + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + 0o3777777777777777777777, + 0o3777777777777777777777, + -0o3777777777777777777777, + 0b11111111111111111111111111111111111111111111111111111111111111111, + 0b11111111111111111111111111111111111111111111111111111111111111111, + -0b11111111111111111111111111111111111111111111111111111111111111111, +} diff --git a/test/behavior/zon/multiline_string.zon b/test/behavior/zon/multiline_string.zon new file mode 100644 index 000000000000..5908802ecc65 --- /dev/null +++ b/test/behavior/zon/multiline_string.zon @@ -0,0 +1,4 @@ +// zig fmt: off + \\Hello, world! +\\This is a multiline string! + \\ There are no escapes, we can, for example, include \n in the string diff --git a/test/behavior/zon/none.zon b/test/behavior/zon/none.zon new file mode 100644 index 000000000000..19765bd501b6 --- /dev/null +++ b/test/behavior/zon/none.zon @@ -0,0 +1 @@ +null diff --git a/test/behavior/zon/slice-1.zon b/test/behavior/zon/slice-1.zon new file mode 100644 index 000000000000..7714116d4567 --- /dev/null +++ b/test/behavior/zon/slice-1.zon @@ -0,0 +1 @@ +.{ 1 } \ No newline at end of file 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/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/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/void.zon b/test/behavior/zon/void.zon new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/test/behavior/zon/void.zon @@ -0,0 +1 @@ +{} \ No newline at end of file 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..0c9f803b977a --- /dev/null +++ b/test/cases/compile_errors/@import_zon_addr_slice.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/addr_slice.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/addr_slice.zon +// +// addr_slice.zon:2:14: error: invalid ZON value 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..c1fe43c612ba --- /dev/null +++ b/test/cases/compile_errors/@import_zon_array_len.zig @@ -0,0 +1,13 @@ +pub fn main() void { + const f: [4]u8 = @import("zon/array.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/array.zon +// +// 2:22: error: expected type '[4]u8', found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}' +// note: destination has length 4 +// note: source has length 3 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..a84f24dd8250 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_bad_import.zig @@ -0,0 +1,12 @@ +pub fn main() void { + _ = @import( + "bogus-does-not-exist.zon", + ); +} + +// error +// backend=stage2 +// target=native +// output_mode=Exe +// +// :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig new file mode 100644 index 000000000000..2a161863eb84 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: *struct { u8, u8, u8 } = @import("zon/array.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/array.zon +// +// found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}' 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..3c3c05e9017e --- /dev/null +++ b/test/cases/compile_errors/@import_zon_double_negation_float.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/double_negation_float.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/double_negation_float.zon +// +// double_negation_float.zon:1:2: error: invalid ZON value 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..ae04a8e8170b --- /dev/null +++ b/test/cases/compile_errors/@import_zon_double_negation_int.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/double_negation_int.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/double_negation_int.zon +// +// double_negation_int.zon:1:2: error: invalid ZON value 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..fa9e31a878ff --- /dev/null +++ b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +pub fn main() void { + const f = @import("zon/enum_embedded_null.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_invalid_character.zig b/test/cases/compile_errors/@import_zon_invalid_character.zig new file mode 100644 index 000000000000..3bf677bd8f98 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_invalid_character.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f = @import("zon/invalid_character.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..dba42b6b1511 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_invalid_number.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f = @import("zon/invalid_number.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..7a6d686a385f --- /dev/null +++ b/test/cases/compile_errors/@import_zon_invalid_string.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f = @import("zon/invalid_string.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..d7673a637f1f --- /dev/null +++ b/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig @@ -0,0 +1,12 @@ +pub fn main() void { + const f = @import("zon/leading_zero_in_integer.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_negative_zero.zig b/test/cases/compile_errors/@import_zon_negative_zero.zig new file mode 100644 index 000000000000..fc67b074842a --- /dev/null +++ b/test/cases/compile_errors/@import_zon_negative_zero.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i8 = @import("zon/negative_zero.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/negative_zero.zon +// +// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous diff --git a/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig b/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig new file mode 100644 index 000000000000..beb2e9bfc595 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: f32 = @import("zon/negative_zero.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/negative_zero.zon +// +// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous 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..18cb13ca9d05 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_number_fail_limits.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i66 = @import("zon/large_number.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/large_number.zon +// +// 2:20: error: type 'i66' cannot represent integer value '36893488147419103232' 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..22c66992e8ca --- /dev/null +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -0,0 +1,12 @@ +const std = @import("std"); +pub fn main() void { + const f = @import("zon/struct_dup_field.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/struct_dup_field.zon +// +// struct_dup_field.zon:3:6: error: duplicate field 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..0035b5da288b --- /dev/null +++ b/test/cases/compile_errors/@import_zon_syntax_error.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: bool = @import("zon/syntax_error.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/syntax_error.zon +// +// syntax_error.zon:3:13: error: expected ',' after initializer 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..4c7eebffca87 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_decl.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/type_decl.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/type_decl.zon +// +// type_decl.zon:2:12: error: invalid ZON value 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..2f57686e530f --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_expr_array.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/type_expr_array.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/type_expr_array.zon +// +// type_expr_array.zon:1:1: error: type expressions not allowed in ZON 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..38c6a36867e1 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_expr_fn.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/type_expr_fn.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/type_expr_fn.zon +// +// type_expr_fn.zon:1:1: error: type expressions not allowed in ZON 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..194baa2057ba --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_expr_struct.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/type_expr_struct.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/type_expr_struct.zon +// +// type_expr_struct.zon:1:1: error: type expressions not allowed in ZON 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..501ed5395142 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/type_expr_tuple.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/type_expr_tuple.zon +// +// type_expr_tuple.zon:1:1: error: type expressions not allowed in ZON 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..5d42fab9b9f8 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_type_mismatch.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: bool = @import("zon/struct.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/struct.zon +// +// 2:21: error: expected type 'bool', found 'struct{comptime boolean: bool = true, comptime number: comptime_int = 123}' 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..d6e188f77c6b --- /dev/null +++ b/test/cases/compile_errors/@import_zon_unescaped_newline.zig @@ -0,0 +1,12 @@ +pub fn main() void { + const f: i8 = @import("zon/unescaped_newline.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/unescaped_newline.zon +// +// unescaped_newline.zon:1:1: error: expected expression, found 'invalid bytes' +// unescaped_newline.zon:1:3: note: invalid byte: '\n' 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..1b983e6053a5 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_unknown_ident.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: i32 = @import("zon/unknown_ident.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/unknown_ident.zon +// +// unknown_ident.zon:2:14: error: use of unknown identifier 'truefalse' 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/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/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/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/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/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/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/src/Cases.zig b/test/src/Cases.zig index 360a0fda9241..79e8f703a1b4 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -90,6 +90,11 @@ pub const Case = struct { link_libc: bool = false, pic: ?bool = null, pie: ?bool = null, + /// A list of imports to cache alongside the source file. + imports: []const []const u8 = &.{}, + /// Where to look for imports relative to the `cases_dir_path` given to + /// `lower_to_build_steps`. If null, file imports will assert. + import_path: ?[]const u8 = null, deps: std.ArrayList(DepModule), @@ -414,6 +419,7 @@ fn addFromDirInner( const pic = try manifest.getConfigForKeyAssertSingle("pic", ?bool); const pie = try manifest.getConfigForKeyAssertSingle("pie", ?bool); const emit_bin = try manifest.getConfigForKeyAssertSingle("emit_bin", bool); + const imports = try manifest.getConfigForKeyAlloc(ctx.arena, "imports", []const u8); if (manifest.type == .translate_c) { for (c_frontends) |c_frontend| { @@ -471,7 +477,7 @@ fn addFromDirInner( const next = ctx.cases.items.len; try ctx.cases.append(.{ .name = std.fs.path.stem(filename), - .target = resolved_target, + .import_path = std.fs.path.dirname(filename), .backend = backend, .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .emit_bin = emit_bin, @@ -481,6 +487,8 @@ fn addFromDirInner( .pic = pic, .pie = pie, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + .imports = imports, + .target = b.resolveTargetQuery(target_query), }); try cases.append(next); } @@ -620,6 +628,7 @@ pub fn lowerToBuildSteps( ) void { const host = std.zig.system.resolveTargetQuery(.{}) catch |err| std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)}); + const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); for (self.incremental_cases.items) |incr_case| { if (true) { @@ -663,11 +672,21 @@ pub fn lowerToBuildSteps( file_sources.put(file.path, writefiles.add(file.path, file.src)) catch @panic("OOM"); } + for (case.imports) |import_rel| { + const import_abs = std.fs.path.join(b.allocator, &.{ + cases_dir_path, + case.import_path orelse @panic("import_path not set"), + import_rel, + }) catch @panic("OOM"); + _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel); + } + const mod = b.createModule(.{ .root_source_file = root_source_file, .target = case.target, .optimize = case.optimize_mode, }); + if (case.link_libc) mod.link_libc = true; if (case.pic) |pic| mod.pic = pic; for (case.deps.items) |dep| { @@ -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 97a25a34fde5387f710197ad7410185c527eeea6 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 16:52:46 -0800 Subject: [PATCH 02/51] Rebases and gets runtime zon import tests passing, comptime import needs to be reworked anyway --- lib/std/zon/parse.zig | 187 +++++++++++++++++++------------------- lib/std/zon/stringify.zig | 70 +++++++------- src/zon.zig | 33 ++++--- 3 files changed, 147 insertions(+), 143 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 10f27dd6fd5c..3a75140502e2 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -359,19 +359,19 @@ test "std.zon parseFromAstNode and parseFromAstNodeNoAlloc" { fn requiresAllocator(comptime T: type) bool { // Keep in sync with parseFree, stringify, and requiresAllocator. return switch (@typeInfo(T)) { - .Pointer => true, - .Array => |Array| requiresAllocator(Array.child), - .Struct => |Struct| inline for (Struct.fields) |field| { + .pointer => true, + .array => |array| requiresAllocator(array.child), + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { if (requiresAllocator(field.type)) { break true; } } else false, - .Union => |Union| inline for (Union.fields) |field| { + .@"union" => |@"union"| inline for (@"union".fields) |field| { if (requiresAllocator(field.type)) { break true; } } else false, - .Optional => |Optional| requiresAllocator(Optional.child), + .optional => |optional| requiresAllocator(optional.child), else => false, }; } @@ -402,25 +402,25 @@ fn maxIdentLength(comptime T: type) usize { // Keep in sync with `parseExpr`. comptime var max = 0; switch (@typeInfo(T)) { - .Bool, .Int, .Float, .Null, .Void => {}, - .Pointer => |Pointer| max = comptime maxIdentLength(Pointer.child), - .Array => |Array| if (Array.len > 0) { - max = comptime maxIdentLength(Array.child); + .bool, .int, .float, .null, .void => {}, + .pointer => |pointer| max = comptime maxIdentLength(pointer.child), + .array => |array| if (array.len > 0) { + max = comptime maxIdentLength(array.child); }, - .Struct => |Struct| inline for (Struct.fields) |field| { - if (!Struct.is_tuple) { + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { + if (!@"struct".is_tuple) { max = @max(max, field.name.len); } max = @max(max, comptime maxIdentLength(field.type)); }, - .Union => |Union| inline for (Union.fields) |field| { + .@"union" => |@"union"| inline for (@"union".fields) |field| { max = @max(max, field.name.len); max = @max(max, comptime maxIdentLength(field.type)); }, - .Enum => |Enum| inline for (Enum.fields) |field| { + .@"enum" => |@"enum"| inline for (@"enum".fields) |field| { max = @max(max, field.name.len); }, - .Optional => |Optional| max = comptime maxIdentLength(Optional.child), + .optional => |optional| max = comptime maxIdentLength(optional.child), else => unreachable, } return max; @@ -493,9 +493,9 @@ pub fn parseFree(gpa: Allocator, value: anytype) void { // Keep in sync with parseFree, stringify, and requiresAllocator. switch (@typeInfo(Value)) { - .Bool, .Int, .Float, .Enum => {}, - .Pointer => |Pointer| { - switch (Pointer.size) { + .bool, .int, .float, .@"enum" => {}, + .pointer => |pointer| { + switch (pointer.size) { .One, .Many, .C => if (comptime requiresAllocator(Value)) { @compileError(@typeName(Value) ++ ": parseFree cannot free non slice pointers"); }, @@ -505,13 +505,13 @@ pub fn parseFree(gpa: Allocator, value: anytype) void { } return gpa.free(value); }, - .Array => for (value) |item| { + .array => for (value) |item| { parseFree(gpa, item); }, - .Struct => |Struct| inline for (Struct.fields) |field| { + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { parseFree(gpa, @field(value, field.name)); }, - .Union => |Union| if (Union.tag_type == null) { + .@"union" => |@"union"| if (@"union".tag_type == null) { if (comptime requiresAllocator(Value)) { @compileError(@typeName(Value) ++ ": parseFree cannot free untagged unions"); } @@ -520,11 +520,11 @@ pub fn parseFree(gpa: Allocator, value: anytype) void { parseFree(gpa, @field(value, @tagName(tag))); }, }, - .Optional => if (value) |some| { + .optional => if (value) |some| { parseFree(gpa, some); }, - .Void => {}, - .Null => {}, + .void => {}, + .null => {}, else => @compileError(@typeName(Value) ++ ": parseFree cannot free this type"), } } @@ -546,18 +546,18 @@ fn parseExpr( // Keep in sync with parseFree, stringify, and requiresAllocator. switch (@typeInfo(T)) { - .Bool => return self.parseBool(node), - .Int, .Float => return self.parseNumber(T, node), - .Enum => return self.parseEnumLiteral(T, node), - .Pointer => return self.parsePointer(T, options, node), - .Array => return self.parseArray(T, options, node), - .Struct => |Struct| if (Struct.is_tuple) + .bool => return self.parseBool(node), + .int, .float => return self.parseNumber(T, node), + .@"enum" => return self.parseEnumLiteral(T, node), + .pointer => return self.parsePointer(T, options, node), + .array => return self.parseArray(T, options, node), + .@"struct" => |@"struct"| if (@"struct".is_tuple) return self.parseTuple(T, options, node) else return self.parseStruct(T, options, node), - .Union => return self.parseUnion(T, options, node), - .Optional => return self.parseOptional(T, options, node), - .Void => return self.parseVoid(node), + .@"union" => return self.parseUnion(T, options, node), + .optional => return self.parseOptional(T, options, node), + .void => return self.parseVoid(node), else => @compileError(@typeName(T) ++ ": cannot parse this type"), } @@ -602,7 +602,7 @@ test "std.zon void" { } fn parseOptional(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Optional = @typeInfo(T).Optional; + const optional = @typeInfo(T).optional; const tags = self.ast.nodes.items(.tag); if (tags[node] == .identifier) { @@ -614,7 +614,7 @@ fn parseOptional(self: *@This(), comptime T: type, comptime options: ParseOption } } - return try self.parseExpr(Optional.child, options, node); + return try self.parseExpr(optional.child, options, node); } test "std.zon optional" { @@ -639,8 +639,8 @@ test "std.zon optional" { } fn parseUnion(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Union = @typeInfo(T).Union; - const field_infos = Union.fields; + const @"union" = @typeInfo(T).@"union"; + const field_infos = @"union".fields; if (field_infos.len == 0) { @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); @@ -661,7 +661,7 @@ fn parseUnion(self: *@This(), comptime T: type, comptime options: ParseOptions, const tags = self.ast.nodes.items(.tag); if (tags[node] == .enum_literal) { // The union must be tagged for an enum literal to coerce to it - if (Union.tag_type == null) { + if (@"union".tag_type == null) { return self.fail(main_tokens[node], .expected_union); } @@ -919,8 +919,8 @@ fn fields( } fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Struct = @typeInfo(T).Struct; - const field_infos = Struct.fields; + const @"struct" = @typeInfo(T).@"struct"; + const field_infos = @"struct".fields; // Gather info on the fields const field_indices = b: { @@ -984,7 +984,7 @@ fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, // Fill in any missing default fields inline for (field_found, 0..) |found, i| { if (!found) { - const field_info = Struct.fields[i]; + const field_info = @"struct".fields[i]; if (field_info.default_value) |default| { const typed: *const field_info.type = @ptrCast(@alignCast(default)); @field(result, field_info.name) = typed.*; @@ -1177,8 +1177,7 @@ test "std.zon structs" { } fn parseTuple(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Struct = @typeInfo(T).Struct; - const field_infos = Struct.fields; + const field_infos = @typeInfo(T).@"struct".fields; var result: T = undefined; @@ -1287,14 +1286,14 @@ test "std.zon tuples" { } fn parseArray(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Array = @typeInfo(T).Array; + const array_info = @typeInfo(T).array; // Parse the array var array: T = undefined; var buf: [2]NodeIndex = undefined; const element_nodes = try self.elements(T, &buf, node); // Check if the size matches - if (element_nodes.len != Array.len) { + if (element_nodes.len != array_info.len) { const main_tokens = self.ast.nodes.items(.main_token); return self.failExpectedContainer(T, main_tokens[node]); } @@ -1308,7 +1307,7 @@ fn parseArray(self: *@This(), comptime T: type, comptime options: ParseOptions, } }; - element.* = try self.parseExpr(Array.child, options, element_node); + element.* = try self.parseExpr(array_info.child, options, element_node); } return array; } @@ -1528,9 +1527,9 @@ fn parsePointer(self: *@This(), comptime T: type, comptime options: ParseOptions } fn parseSlice(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Ptr = @typeInfo(T).Pointer; + const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice - switch (Ptr.size) { + switch (pointer.size) { .Slice => {}, .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), } @@ -1541,11 +1540,11 @@ fn parseSlice(self: *@This(), comptime T: type, comptime options: ParseOptions, const element_nodes = try self.elements(T, &buf, node); // Allocate the slice - const sentinel = if (Ptr.sentinel) |s| @as(*const Ptr.child, @ptrCast(s)).* else null; + const sentinel = if (pointer.sentinel) |s| @as(*const pointer.child, @ptrCast(s)).* else null; const slice = self.gpa.allocWithOptions( - Ptr.child, + pointer.child, element_nodes.len, - Ptr.alignment, + pointer.alignment, sentinel, ) catch |err| switch (err) { error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), @@ -1559,15 +1558,15 @@ fn parseSlice(self: *@This(), comptime T: type, comptime options: ParseOptions, parseFree(self.gpa, slice[i]); } }; - element.* = try self.parseExpr(Ptr.child, options, element_node); + element.* = try self.parseExpr(pointer.child, options, element_node); } return slice; } fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const Pointer = @typeInfo(T).Pointer; + const pointer = @typeInfo(T).pointer; - if (Pointer.size != .Slice) { + if (pointer.size != .Slice) { @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); } @@ -1575,7 +1574,7 @@ fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ const token = main_tokens[node]; const raw = self.ast.tokenSlice(token); - if (Pointer.child != u8 or !Pointer.is_const or Pointer.alignment != 1) { + if (pointer.child != u8 or !pointer.is_const or pointer.alignment != 1) { return self.failExpectedContainer(T, token); } var buf = std.ArrayListUnmanaged(u8){}; @@ -1591,7 +1590,7 @@ fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ .failure => |reason| return self.failInvalidStringLiteral(token, reason), } - if (Pointer.sentinel) |sentinel| { + if (pointer.sentinel) |sentinel| { if (@as(*const u8, @ptrCast(sentinel)).* != 0) { return self.failExpectedContainer(T, token); } @@ -1608,13 +1607,13 @@ fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ fn parseMultilineStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { const main_tokens = self.ast.nodes.items(.main_token); - const Pointer = @typeInfo(T).Pointer; + const pointer = @typeInfo(T).pointer; - if (Pointer.size != .Slice) { + if (pointer.size != .Slice) { @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); } - if (Pointer.child != u8 or !Pointer.is_const or Pointer.alignment != 1) { + if (pointer.child != u8 or !pointer.is_const or pointer.alignment != 1) { return self.failExpectedContainer(T, main_tokens[node]); } @@ -1632,7 +1631,7 @@ fn parseMultilineStringLiteral(self: *@This(), comptime T: type, node: NodeIndex }; } - if (Pointer.sentinel) |sentinel| { + if (pointer.sentinel) |sentinel| { if (@as(*const u8, @ptrCast(sentinel)).* != 0) { return self.failExpectedContainer(T, main_tokens[node]); } @@ -1878,7 +1877,7 @@ fn parseEnumLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type switch (tags[node]) { .enum_literal => { // Create a comptime string map for the enum fields - const enum_fields = @typeInfo(T).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) }; @@ -1986,7 +1985,7 @@ test "std.zon enum literals" { } fn fail(self: @This(), token: TokenIndex, reason: ParseFailure.Reason) error{Type} { - @setCold(true); + @branchHint(.cold); if (self.status) |s| s.* = .{ .failure = .{ .ast = self.ast, .token = token, @@ -2006,35 +2005,35 @@ fn failOutOfMemory(self: *@This(), token: TokenIndex) error{ParserOutOfMemory} { } fn failInvalidStringLiteral(self: @This(), token: TokenIndex, err: StringLiteralError) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .{ .invalid_string_literal = .{ .err = err }, }); } fn failInvalidNumberLiteral(self: @This(), token: TokenIndex, err: NumberLiteralError) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .{ .invalid_number_literal = .{ .err = err }, }); } fn failCannotRepresent(self: @This(), comptime T: type, token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .{ .cannot_represent = .{ .type_name = @typeName(T) }, }); } fn failNegativeIntegerZero(self: @This(), token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .negative_integer_zero); } fn failUnexpectedField(self: @This(), T: type, token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); switch (@typeInfo(T)) { - .Struct, .Union, .Enum => return self.fail(token, .{ .unexpected_field = .{ + .@"struct", .@"union", .@"enum" => return self.fail(token, .{ .unexpected_field = .{ .fields = std.meta.fieldNames(T), } }), else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), @@ -2042,25 +2041,25 @@ fn failUnexpectedField(self: @This(), T: type, token: TokenIndex) error{Type} { } fn failExpectedContainer(self: @This(), T: type, token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); switch (@typeInfo(T)) { - .Struct => |Struct| if (Struct.is_tuple) { + .@"struct" => |@"struct"| if (@"struct".is_tuple) { return self.fail(token, .{ .expected_tuple_with_fields = .{ - .fields = Struct.fields.len, + .fields = @"struct".fields.len, } }); } else { return self.fail(token, .expected_struct); }, - .Union => return self.fail(token, .expected_union), - .Array => |Array| return self.fail(token, .{ .expected_tuple_with_fields = .{ - .fields = Array.len, + .@"union" => return self.fail(token, .expected_union), + .array => |array| return self.fail(token, .{ .expected_tuple_with_fields = .{ + .fields = array.len, } }), - .Pointer => |Pointer| { - if (Pointer.child == u8 and - Pointer.size == .Slice and - Pointer.is_const and - (Pointer.sentinel == null or @as(*const u8, @ptrCast(Pointer.sentinel)).* == 0) and - Pointer.alignment == 1) + .pointer => |pointer| { + if (pointer.child == u8 and + pointer.size == .Slice and + pointer.is_const and + (pointer.sentinel == null or @as(*const u8, @ptrCast(pointer.sentinel)).* == 0) and + pointer.alignment == 1) { return self.fail(token, .expected_string); } else { @@ -2072,17 +2071,17 @@ fn failExpectedContainer(self: @This(), T: type, token: TokenIndex) error{Type} } fn failMissingField(self: @This(), name: []const u8, token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .{ .missing_field = .{ .field_name = name } }); } fn failDuplicateField(self: @This(), token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .duplicate_field); } fn failTypeExpr(self: @This(), token: TokenIndex) error{Type} { - @setCold(true); + @branchHint(.cold); return self.fail(token, .type_expr); } @@ -2146,7 +2145,7 @@ fn parseNumber( .number_literal => return self.parseNumberLiteral(T, node), .char_literal => return self.parseCharLiteral(T, node), .identifier => switch (@typeInfo(T)) { - .Float => { + .float => { const token = main_tokens[num_lit_node]; const bytes = self.ast.tokenSlice(token); const Ident = enum { inf, nan }; @@ -2196,7 +2195,7 @@ fn applySignToInt(self: @This(), comptime T: type, node: NodeIndex, int: anytype return self.failNegativeIntegerZero(main_tokens[node]); } switch (@typeInfo(T)) { - .Int => |int_type| switch (int_type.signedness) { + .int => |int_info| switch (int_info.signedness) { .signed => { const In = @TypeOf(int); if (std.math.maxInt(In) > std.math.maxInt(T) and int == @as(In, std.math.maxInt(T)) + 1) { @@ -2207,14 +2206,14 @@ fn applySignToInt(self: @This(), comptime T: type, node: NodeIndex, int: anytype }, .unsigned => return self.failCannotRepresent(T, main_tokens[node]), }, - .Float => return -@as(T, @floatFromInt(int)), + .float => return -@as(T, @floatFromInt(int)), else => @compileError("internal error: expected numeric type"), } } else { switch (@typeInfo(T)) { - .Int => return std.math.cast(T, int) orelse + .int => return std.math.cast(T, int) orelse self.failCannotRepresent(T, main_tokens[node]), - .Float => return @as(T, @floatFromInt(int)), + .float => return @as(T, @floatFromInt(int)), else => @compileError("internal error: expected numeric type"), } } @@ -2227,8 +2226,8 @@ fn parseBigNumber( base: Base, ) error{Type}!T { switch (@typeInfo(T)) { - .Int => return self.parseBigInt(T, node, base), - .Float => { + .int => return self.parseBigInt(T, node, base), + .float => { const result = @as(T, @floatCast(try self.parseFloat(f128, node))); if (std.math.isNegativeZero(result)) { const main_tokens = self.ast.nodes.items(.main_token); @@ -2265,12 +2264,12 @@ fn parseFloat( const main_tokens = self.ast.nodes.items(.main_token); const num_lit_token = main_tokens[num_lit_node]; const bytes = self.ast.tokenSlice(num_lit_token); - const Float = if (@typeInfo(T) == .Float) T else f128; + const Float = if (@typeInfo(T) == .float) T else f128; const unsigned_float = std.fmt.parseFloat(Float, bytes) catch unreachable; // Already validated const result = if (self.isNegative(node)) -unsigned_float else unsigned_float; switch (@typeInfo(T)) { - .Float => return @as(T, @floatCast(result)), - .Int => return intFromFloatExact(T, result) orelse + .float => return @as(T, @floatCast(result)), + .int => return intFromFloatExact(T, result) orelse return self.failCannotRepresent(T, main_tokens[node]), else => @compileError("internal error: expected integer or float type"), } @@ -2301,11 +2300,11 @@ fn numLitNode(self: *const @This(), node: NodeIndex) NodeIndex { fn intFromFloatExact(comptime T: type, value: anytype) ?T { switch (@typeInfo(@TypeOf(value))) { - .Float => {}, + .float => {}, else => @compileError(@typeName(@TypeOf(value)) ++ " is not a runtime floating point type"), } switch (@typeInfo(T)) { - .Int => {}, + .int => {}, else => @compileError(@typeName(T) ++ " is not a runtime integer type"), } diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 81ebc065f84b..2f6c7e7b761e 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -129,7 +129,7 @@ fn typeIsRecursiveImpl(comptime T: type, comptime visited_arg: []type) bool { } // Add this type to the stack - if (visited.len >= @typeInfo(RecursiveTypeBuffer).Array.len) { + if (visited.len >= @typeInfo(RecursiveTypeBuffer).array.len) { @compileError("recursion limit"); } visited.ptr[visited.len] = T; @@ -137,19 +137,19 @@ fn typeIsRecursiveImpl(comptime T: type, comptime visited_arg: []type) bool { // Recurse switch (@typeInfo(T)) { - .Pointer => |Pointer| return typeIsRecursiveImpl(Pointer.child, visited), - .Array => |Array| return typeIsRecursiveImpl(Array.child, visited), - .Struct => |Struct| inline for (Struct.fields) |field| { + .pointer => |pointer| return typeIsRecursiveImpl(pointer.child, visited), + .array => |array| return typeIsRecursiveImpl(array.child, visited), + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { if (typeIsRecursiveImpl(field.type, visited)) { return true; } }, - .Union => |Union| inline for (Union.fields) |field| { + .@"union" => |@"union"| inline for (@"union".fields) |field| { if (typeIsRecursiveImpl(field.type, visited)) { return true; } }, - .Optional => |Optional| return typeIsRecursiveImpl(Optional.child, visited), + .optional => |optional| return typeIsRecursiveImpl(optional.child, visited), else => {}, } return false; @@ -183,27 +183,27 @@ fn checkValueDepth(val: anytype, depth: usize) error{MaxDepth}!void { const child_depth = depth - 1; switch (@typeInfo(@TypeOf(val))) { - .Pointer => |Pointer| switch (Pointer.size) { + .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| { + .array => for (val) |item| { try checkValueDepth(item, child_depth); }, - .Struct => |Struct| inline for (Struct.fields) |field_info| { + .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| { try checkValueDepth(@field(val, field_info.name), child_depth); }, - .Union => |Union| if (Union.tag_type == null) { + .@"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), + .optional => if (val) |inner| try checkValueDepth(inner, child_depth), else => {}, } } @@ -311,9 +311,9 @@ pub fn Stringifier(comptime Writer: type) type { /// Serialize a value, similar to `stringifyArbitraryDepth`. pub fn valueArbitraryDepth(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { switch (@typeInfo(@TypeOf(val))) { - .Int => |Int| if (options.emit_utf8_codepoints and - Int.signedness == .unsigned and - Int.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) + .int => |int_info| if (options.emit_utf8_codepoints and + int_info.signedness == .unsigned and + int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) { self.utf8Codepoint(val) catch |err| switch (err) { error.InvalidCodepoint => unreachable, // Already validated @@ -322,7 +322,7 @@ pub fn Stringifier(comptime Writer: type) type { } else { try self.int(val); }, - .ComptimeInt => if (options.emit_utf8_codepoints and + .comptime_int => if (options.emit_utf8_codepoints and val > 0 and val <= std.math.maxInt(u21) and std.unicode.utf8ValidCodepoint(val)) @@ -334,23 +334,23 @@ pub fn Stringifier(comptime Writer: type) type { } else { try self.int(val); }, - .Float, .ComptimeFloat => try self.float(val), - .Bool, .Null => try std.fmt.format(self.writer, "{}", .{val}), - .EnumLiteral => { + .float, .comptime_float => try self.float(val), + .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), + .enum_literal => { try self.writer.writeByte('.'); try self.ident(@tagName(val)); }, - .Enum => |Enum| if (Enum.is_exhaustive) { + .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { try self.writer.writeByte('.'); try self.ident(@tagName(val)); } else { @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums"); }, - .Void => try self.writer.writeAll("{}"), - .Pointer => |Pointer| { - const child_type = switch (@typeInfo(Pointer.child)) { - .Array => |Array| Array.child, - else => if (Pointer.size != .Slice) @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type") else Pointer.child, + .void => try self.writer.writeAll("{}"), + .pointer => |pointer| { + const child_type = switch (@typeInfo(pointer.child)) { + .array => |array| array.child, + else => if (pointer.size != .Slice) @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type") else pointer.child, }; if (child_type == u8 and !options.emit_strings_as_containers) { try self.string(val); @@ -358,15 +358,15 @@ pub fn Stringifier(comptime Writer: type) type { try self.sliceImpl(val, options); } }, - .Array => { + .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 => |StructInfo| if (StructInfo.is_tuple) { - var container = try self.startTuple(.{ .whitespace_style = .{ .fields = StructInfo.fields.len } }); + .@"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); } @@ -374,11 +374,11 @@ pub fn Stringifier(comptime Writer: type) type { } else { // Decide which fields to emit const fields, const skipped = if (options.emit_default_optional_fields) b: { - break :b .{ StructInfo.fields.len, [1]bool{false} ** StructInfo.fields.len }; + break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; } else b: { - var fields = StructInfo.fields.len; - var skipped = [1]bool{false} ** StructInfo.fields.len; - inline for (StructInfo.fields, &skipped) |field_info, *skip| { + var fields = @"struct".fields.len; + var skipped = [1]bool{false} ** @"struct".fields.len; + inline for (@"struct".fields, &skipped) |field_info, *skip| { if (field_info.default_value) |default_field_value_opaque| { const field_value = @field(val, field_info.name); const default_field_value: *const @TypeOf(field_value) = @ptrCast(@alignCast(default_field_value_opaque)); @@ -393,14 +393,14 @@ pub fn Stringifier(comptime Writer: type) type { // Emit those fields var container = try self.startStruct(.{ .whitespace_style = .{ .fields = fields } }); - inline for (StructInfo.fields, skipped) |field_info, skip| { + inline for (@"struct".fields, skipped) |field_info, skip| { if (!skip) { try container.fieldArbitraryDepth(field_info.name, @field(val, field_info.name), options); } } try container.finish(); }, - .Union => |Union| if (Union.tag_type == null) { + .@"union" => |@"union"| if (@"union".tag_type == null) { @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); } else { var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); @@ -409,7 +409,7 @@ pub fn Stringifier(comptime Writer: type) type { } try container.finish(); }, - .Optional => if (val) |inner| { + .optional => if (val) |inner| { try self.valueArbitraryDepth(inner, options); } else { try self.writer.writeAll("null"); @@ -427,7 +427,7 @@ pub fn Stringifier(comptime Writer: type) type { /// Serialize a float. pub fn float(self: *Self, val: anytype) Writer.Error!void { switch (@typeInfo(@TypeOf(val))) { - .Float, .ComptimeFloat => if (std.math.isNan(val)) { + .float, .comptime_float => if (std.math.isNan(val)) { return self.writer.writeAll("nan"); } else if (@as(f128, val) == std.math.inf(f128)) { return self.writer.writeAll("inf"); diff --git a/src/zon.zig b/src/zon.zig index 7502060c0eed..2fd927735c28 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -29,19 +29,24 @@ pub fn lower( file_index: Zcu.File.Index, res_ty: ?Type, ) CompileError!InternPool.Index { - const lower_zon: LowerZon = .{ - .sema = sema, - .file = file, - .file_index = file_index, - }; - const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated - if (tree.errors.len != 0) { - return lower_zon.lowerAstErrors(); - } - - const data = tree.nodes.items(.data); - const root = data[0].lhs; - return lower_zon.expr(root, res_ty); + _ = sema; + _ = file; + _ = file_index; + _ = res_ty; + @panic("unimplemented"); + // const lower_zon: LowerZon = .{ + // .sema = sema, + // .file = file, + // .file_index = file_index, + // }; + // const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated + // if (tree.errors.len != 0) { + // return lower_zon.lowerAstErrors(); + // } + + // const data = tree.nodes.items(.data); + // const root = data[0].lhs; + // return lower_zon.expr(root, res_ty); } fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { @@ -61,7 +66,7 @@ fn fail( comptime format: []const u8, args: anytype, ) (Allocator.Error || error{AnalysisFail}) { - @setCold(true); + @branchHint(.cold); const src_loc = try self.lazySrcLoc(loc); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); From 3671342cef0ac38d36e1fa2f47eb5ddc427d3c38 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 17:47:08 -0800 Subject: [PATCH 03/51] Gets some import zon behavior tests working, skips failing tests for now --- src/zon.zig | 137 ++++++++------- test/behavior/zon.zig | 400 ++++++++++++++++++++++-------------------- 2 files changed, 277 insertions(+), 260 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 2fd927735c28..4ad92faf3e5e 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -29,24 +29,19 @@ pub fn lower( file_index: Zcu.File.Index, res_ty: ?Type, ) CompileError!InternPool.Index { - _ = sema; - _ = file; - _ = file_index; - _ = res_ty; - @panic("unimplemented"); - // const lower_zon: LowerZon = .{ - // .sema = sema, - // .file = file, - // .file_index = file_index, - // }; - // const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated - // if (tree.errors.len != 0) { - // return lower_zon.lowerAstErrors(); - // } - - // const data = tree.nodes.items(.data); - // const root = data[0].lhs; - // return lower_zon.expr(root, res_ty); + const lower_zon: LowerZon = .{ + .sema = sema, + .file = file, + .file_index = file_index, + }; + const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated + if (tree.errors.len != 0) { + return lower_zon.lowerAstErrors(); + } + + const data = tree.nodes.items(.data); + const root = data[0].lhs; + return lower_zon.expr(root, res_ty); } fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { @@ -215,7 +210,7 @@ const FieldTypes = union(enum) { const t = ty orelse return .none; const ip = &sema.pt.zcu.intern_pool; switch (t.zigTypeTagOrPoison(sema.pt.zcu) catch return .none) { - .Struct => { + .@"struct" => { try t.resolveFully(sema.pt); const loaded_struct_type = ip.loadStructType(t.toIntern()); return .{ .st = .{ @@ -223,7 +218,7 @@ const FieldTypes = union(enum) { .loaded = loaded_struct_type, } }; }, - .Union => { + .@"union" => { try t.resolveFully(sema.pt); const loaded_union_type = ip.loadUnionType(t.toIntern()); const loaded_tag_type = loaded_union_type.loadTagType(ip); @@ -243,7 +238,7 @@ const FieldTypes = union(enum) { .un => |un| .{ un.ty, un.loaded.nameIndex(ip, name) orelse return null }, .none => return null, }; - return self_ty.structFieldType(index, zcu); + return self_ty.fieldType(index, zcu); } }; @@ -273,14 +268,16 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { .address_space = .generic, }, }); - return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ - .ty = ptr_type.toIntern(), - .base_addr = .{ .anon_decl = .{ - .orig_ty = ptr_type.toIntern(), - .val = val, - } }, - .byte_offset = 0, - } }); + _ = ptr_type; + @panic("unimplemented"); + // return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ + // .ty = ptr_type.toIntern(), + // .base_addr = .{ .anon_decl = .{ + // .orig_ty = ptr_type.toIntern(), + // .val = val, + // } }, + // .byte_offset = 0, + // } }); } } @@ -358,14 +355,17 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { .address_space = .generic, }, }); - return self.sema.pt.intern(.{ .ptr = .{ - .ty = ptr_ty.toIntern(), - .base_addr = .{ .anon_decl = .{ - .val = array_val, - .orig_ty = ptr_ty.toIntern(), - } }, - .byte_offset = 0, - } }); + _ = array_val; + _ = ptr_ty; + @panic("unimplemented"); + // return self.sema.pt.intern(.{ .ptr = .{ + // .ty = ptr_ty.toIntern(), + // .base_addr = .{ .anon_decl = .{ + // .val = array_val, + // .orig_ty = ptr_ty.toIntern(), + // } }, + // .byte_offset = 0, + // } }); }, .multiline_string_literal => { var bytes = std.ArrayListUnmanaged(u8){}; @@ -392,14 +392,17 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { .address_space = .generic, }, }); - return self.sema.pt.intern(.{ .ptr = .{ - .ty = ptr_ty.toIntern(), - .base_addr = .{ .anon_decl = .{ - .val = array_val, - .orig_ty = ptr_ty.toIntern(), - } }, - .byte_offset = 0, - } }); + _ = array_val; + _ = ptr_ty; + @panic("unimplemented"); + // return self.sema.pt.intern(.{ .ptr = .{ + // .ty = ptr_ty.toIntern(), + // .base_addr = .{ .anon_decl = .{ + // .val = array_val, + // .orig_ty = ptr_ty.toIntern(), + // } }, + // .byte_offset = 0, + // } }); }, .struct_init_one, .struct_init_one_comma, @@ -440,15 +443,16 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { types[i] = ip.typeOf(values[i]); } - const struct_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ - .types = types, - .names = names.entries.items(.key), - .values = values, - }); - return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ - .ty = struct_type, - .storage = .{ .elems = values }, - } }); + @panic("unimplemented"); + // const struct_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ + // .types = types, + // .names = names.entries.items(.key), + // .values = values, + // }); + // return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ + // .ty = struct_type, + // .storage = .{ .elems = values }, + // } }); }, .array_init_one, .array_init_one_comma, @@ -472,11 +476,11 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { const elem_ty = if (res_ty) |rt| b: { const type_tag = rt.zigTypeTagOrPoison(self.sema.pt.zcu) catch break :b null; switch (type_tag) { - .Array => break :b rt.childType(self.sema.pt.zcu), - .Struct => { + .array => break :b rt.childType(self.sema.pt.zcu), + .@"struct" => { try rt.resolveFully(self.sema.pt); if (i >= rt.structFieldCount(self.sema.pt.zcu)) break :b null; - break :b rt.structFieldType(i, self.sema.pt.zcu); + break :b rt.fieldType(i, self.sema.pt.zcu); }, else => break :b null, } @@ -485,15 +489,16 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { types[i] = ip.typeOf(values[i]); } - const tuple_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ - .types = types, - .names = &.{}, - .values = values, - }); - return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ - .ty = tuple_type, - .storage = .{ .elems = values }, - } }); + @panic("unimplemented"); + // const tuple_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ + // .types = types, + // .names = &.{}, + // .values = values, + // }); + // return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ + // .ty = tuple_type, + // .storage = .{ .elems = values }, + // } }); }, .block_two => if (data[node].lhs == 0 and data[node].rhs == 0) { return .void_value; diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 9e503f1a67f8..d23122aab989 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -30,46 +30,51 @@ test "optional" { } test "union" { - const Union = union { - x: f32, - y: bool, - }; + return error.SkipZigTest; + // const Union = union { + // x: f32, + // y: bool, + // }; - const union1: Union = @import("zon/union1.zon"); - const union2: Union = @import("zon/union2.zon"); + // const union1: Union = @import("zon/union1.zon"); + // const union2: Union = @import("zon/union2.zon"); - try expectEqual(union1.x, 1.5); - try expectEqual(union2.y, true); + // try expectEqual(union1.x, 1.5); + // try expectEqual(union2.y, true); } test "struct" { - try expectEqual(.{}, @import("zon/vec0.zon")); - try expectEqual(.{ .x = 1.5 }, @import("zon/vec1.zon")); - try expectEqual(.{ .x = 1.5, .y = 2 }, @import("zon/vec2.zon")); - try expectEqual(.{ .@"0" = 1.5, .foo = 2 }, @import("zon/escaped_struct.zon")); - try expectEqual(.{}, @import("zon/empty_struct.zon")); + return error.SkipZigTest; + // try expectEqual(.{}, @import("zon/vec0.zon")); + // try expectEqual(.{ .x = 1.5 }, @import("zon/vec1.zon")); + // try expectEqual(.{ .x = 1.5, .y = 2 }, @import("zon/vec2.zon")); + // try expectEqual(.{ .@"0" = 1.5, .foo = 2 }, @import("zon/escaped_struct.zon")); + // try expectEqual(.{}, @import("zon/empty_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); + return error.SkipZigTest; + // 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); } test "struct enum field" { - const Struct = struct { - x: enum { x, y, z }, - }; - try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon"))); + return error.SkipZigTest; + // const Struct = struct { + // x: enum { x, y, z }, + // }; + // try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon"))); } test "tuple" { - try expectEqualDeep(.{ 1.2, true, "hello", 3 }, @import("zon/tuple.zon")); + return error.SkipZigTest; + // try expectEqualDeep(.{ 1.2, true, "hello", 3 }, @import("zon/tuple.zon")); } test "char" { @@ -79,190 +84,197 @@ test "char" { } test "arrays" { - try expectEqual([0]u8{}, @import("zon/vec0.zon")); - try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); - try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); + return error.SkipZigTest; + // try expectEqual([0]u8{}, @import("zon/vec0.zon")); + // try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); + // try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); } 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/slice-1.zon"); - try expectEqualSlices(u8, expected_slice, found_slice); - - const expected_array: [1]u8 = .{1}; - const found_array: [1]u8 = @import("zon/slice-1.zon"); - try expectEqual(expected_array, found_array); - - const T = struct { u8 }; - const expected_tuple: T = .{1}; - const found_tuple: T = @import("zon/slice-1.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); - } + return error.SkipZigTest; + // { + // 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/slice-1.zon"); + // try expectEqualSlices(u8, expected_slice, found_slice); + + // const expected_array: [1]u8 = .{1}; + // const found_array: [1]u8 = @import("zon/slice-1.zon"); + // try expectEqual(expected_array, found_array); + + // const T = struct { u8 }; + // const expected_tuple: T = .{1}; + // const found_tuple: T = @import("zon/slice-1.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" { - // const foo: [3]u8 = "foo".*; - // const bar: []const u8 = &foo; - 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 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")); + return error.SkipZigTest; + // // const foo: [3]u8 = "foo".*; + // // const bar: []const u8 = &foo; + // 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 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, @import("zon/foo.zon")); - try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); + return error.SkipZigTest; + // const Enum = enum { + // foo, + // bar, + // baz, + // @"0\na", + // }; + // try expectEqual(Enum.foo, @import("zon/foo.zon")); + // try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); } test "int" { - const expected = .{ - // Test various numbers and types - @as(u8, 10), - @as(i16, 24), - @as(i14, -4), - @as(i32, -123), - - // Test limits - @as(i8, 127), - @as(i8, -128), - - // Test characters - @as(u8, 'a'), - @as(u8, 'z'), - - // Test big integers - @as(u65, 36893488147419103231), - @as(u65, 36893488147419103231), - @as(i128, -18446744073709551615), // Only a big int due to negation - @as(i128, -9223372036854775809), // Only a big int due to negation - - // Test big integer limits - @as(i66, 36893488147419103231), - @as(i66, -36893488147419103232), - - // Test parsing whole number floats as integers - @as(i8, -1), - @as(i8, 123), - - // Test non-decimal integers - @as(i16, 0xff), - @as(i16, -0xff), - @as(i16, 0o77), - @as(i16, -0o77), - @as(i16, 0b11), - @as(i16, -0b11), - - // Test non-decimal big integers - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), - }; - const actual: @TypeOf(expected) = @import("zon/ints.zon"); - try expectEqual(expected, actual); + return error.SkipZigTest; + // const expected = .{ + // // Test various numbers and types + // @as(u8, 10), + // @as(i16, 24), + // @as(i14, -4), + // @as(i32, -123), + + // // Test limits + // @as(i8, 127), + // @as(i8, -128), + + // // Test characters + // @as(u8, 'a'), + // @as(u8, 'z'), + + // // Test big integers + // @as(u65, 36893488147419103231), + // @as(u65, 36893488147419103231), + // @as(i128, -18446744073709551615), // Only a big int due to negation + // @as(i128, -9223372036854775809), // Only a big int due to negation + + // // Test big integer limits + // @as(i66, 36893488147419103231), + // @as(i66, -36893488147419103232), + + // // Test parsing whole number floats as integers + // @as(i8, -1), + // @as(i8, 123), + + // // Test non-decimal integers + // @as(i16, 0xff), + // @as(i16, -0xff), + // @as(i16, 0o77), + // @as(i16, -0o77), + // @as(i16, 0b11), + // @as(i16, -0b11), + + // // Test non-decimal big integers + // @as(u65, 0x1ffffffffffffffff), + // @as(i66, 0x1ffffffffffffffff), + // @as(i66, -0x1ffffffffffffffff), + // @as(u65, 0x1ffffffffffffffff), + // @as(i66, 0x1ffffffffffffffff), + // @as(i66, -0x1ffffffffffffffff), + // @as(u65, 0x1ffffffffffffffff), + // @as(i66, 0x1ffffffffffffffff), + // @as(i66, -0x1ffffffffffffffff), + // }; + // const actual: @TypeOf(expected) = @import("zon/ints.zon"); + // try expectEqual(expected, actual); } test "floats" { - const expected = .{ - // Test decimals - @as(f16, 0.5), - @as(f32, 123.456), - @as(f64, -123.456), - @as(f128, 42.5), - - // Test whole numbers with and without decimals - @as(f16, 5.0), - @as(f16, 5.0), - @as(f32, -102), - @as(f32, -102), - - // Test characters and negated characters - @as(f32, 'a'), - @as(f32, 'z'), - @as(f32, -'z'), - - // Test big integers - @as(f32, 36893488147419103231), - @as(f32, -36893488147419103231), - @as(f128, 0x1ffffffffffffffff), - @as(f32, 0x1ffffffffffffffff), - - // Exponents, underscores - @as(f32, 123.0E+77), - - // Hexadecimal - @as(f32, 0x103.70p-5), - @as(f32, -0x103.70), - @as(f32, 0x1234_5678.9ABC_CDEFp-10), - }; - const actual: @TypeOf(expected) = @import("zon/floats.zon"); - try expectEqual(actual, expected); + return error.SkipZigTest; + // const expected = .{ + // // Test decimals + // @as(f16, 0.5), + // @as(f32, 123.456), + // @as(f64, -123.456), + // @as(f128, 42.5), + + // // Test whole numbers with and without decimals + // @as(f16, 5.0), + // @as(f16, 5.0), + // @as(f32, -102), + // @as(f32, -102), + + // // Test characters and negated characters + // @as(f32, 'a'), + // @as(f32, 'z'), + // @as(f32, -'z'), + + // // Test big integers + // @as(f32, 36893488147419103231), + // @as(f32, -36893488147419103231), + // @as(f128, 0x1ffffffffffffffff), + // @as(f32, 0x1ffffffffffffffff), + + // // Exponents, underscores + // @as(f32, 123.0E+77), + + // // Hexadecimal + // @as(f32, 0x103.70p-5), + // @as(f32, -0x103.70), + // @as(f32, 0x1234_5678.9ABC_CDEFp-10), + // }; + // const actual: @TypeOf(expected) = @import("zon/floats.zon"); + // try expectEqual(actual, expected); } test "inf and nan" { - // comptime float - { - const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); - try expect(std.math.isNan(actual[0])); - try expect(std.math.isNan(actual[1])); - try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); - try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); - } - - // f32 - { - const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); - try expect(std.math.isNan(actual[0])); - try expect(std.math.isNan(actual[1])); - try expect(std.math.isPositiveInf(actual[2])); - try expect(std.math.isNegativeInf(actual[3])); - } + return error.SkipZigTest; + // // comptime float + // { + // const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); + // try expect(std.math.isNan(actual[0])); + // try expect(std.math.isNan(actual[1])); + // try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); + // try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); + // } + + // // f32 + // { + // const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + // try expect(std.math.isNan(actual[0])); + // try expect(std.math.isNan(actual[1])); + // try expect(std.math.isPositiveInf(actual[2])); + // try expect(std.math.isNegativeInf(actual[3])); + // } } From 6e10c3827a07d36a730528d3aaf74b1cd1019022 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 19:18:42 -0800 Subject: [PATCH 04/51] Gets strings working again --- src/zon.zig | 87 ++++++++++++++++++------------------------- test/behavior/zon.zig | 40 +++++++++----------- 2 files changed, 55 insertions(+), 72 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 4ad92faf3e5e..2a2cf33f36f3 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -335,37 +335,28 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { ); }, } - - const array_ty = try self.sema.pt.arrayType(.{ + const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ .len = bytes.items.len, .sentinel = .zero_u8, .child = .u8_type, - }); + } }); const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty.toIntern(), - .storage = .{ - .bytes = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls), - }, + .ty = array_ty, + .storage = .{ .bytes = string }, + } }); + return self.sema.pt.intern(.{ .slice = .{ + .ty = .slice_const_u8_sentinel_0_type, + .ptr = try self.sema.pt.intern(.{ .ptr = .{ + .ty = .manyptr_const_u8_sentinel_0_type, + .base_addr = .{ .uav = .{ + .orig_ty = .slice_const_u8_sentinel_0_type, + .val = array_val, + } }, + .byte_offset = 0, + } }), + .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), } }); - const ptr_ty = try self.sema.pt.ptrTypeSema(.{ - .child = array_ty.toIntern(), - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - }); - _ = array_val; - _ = ptr_ty; - @panic("unimplemented"); - // return self.sema.pt.intern(.{ .ptr = .{ - // .ty = ptr_ty.toIntern(), - // .base_addr = .{ .anon_decl = .{ - // .val = array_val, - // .orig_ty = ptr_ty.toIntern(), - // } }, - // .byte_offset = 0, - // } }); }, .multiline_string_literal => { var bytes = std.ArrayListUnmanaged(u8){}; @@ -377,32 +368,28 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { try parser.line(self.file.tree.tokenSlice(tok_i)); } - const array_ty = try self.sema.pt.arrayType(.{ .len = bytes.items.len, .sentinel = .zero_u8, .child = .u8_type }); + const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ + .len = bytes.items.len, + .sentinel = .zero_u8, + .child = .u8_type, + } }); const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty.toIntern(), - .storage = .{ - .bytes = (try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .no_embedded_nulls)).toString(), - }, + .ty = array_ty, + .storage = .{ .bytes = string }, + } }); + return self.sema.pt.intern(.{ .slice = .{ + .ty = .slice_const_u8_sentinel_0_type, + .ptr = try self.sema.pt.intern(.{ .ptr = .{ + .ty = .manyptr_const_u8_sentinel_0_type, + .base_addr = .{ .uav = .{ + .orig_ty = .slice_const_u8_sentinel_0_type, + .val = array_val, + } }, + .byte_offset = 0, + } }), + .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), } }); - const ptr_ty = try self.sema.pt.ptrTypeSema(.{ - .child = array_ty.toIntern(), - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - }); - _ = array_val; - _ = ptr_ty; - @panic("unimplemented"); - // return self.sema.pt.intern(.{ .ptr = .{ - // .ty = ptr_ty.toIntern(), - // .base_addr = .{ .anon_decl = .{ - // .val = array_val, - // .orig_ty = ptr_ty.toIntern(), - // } }, - // .byte_offset = 0, - // } }); }, .struct_init_one, .struct_init_one_comma, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index d23122aab989..b10396a53c38 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -139,31 +139,27 @@ test "slices, arrays, tuples" { } test "string literals" { - return error.SkipZigTest; - // // const foo: [3]u8 = "foo".*; - // // const bar: []const u8 = &foo; - // 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 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")); + 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 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" { - return error.SkipZigTest; - // const Enum = enum { - // foo, - // bar, - // baz, - // @"0\na", - // }; - // try expectEqual(Enum.foo, @import("zon/foo.zon")); - // try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); + const Enum = enum { + foo, + bar, + baz, + @"0\na", + }; + try expectEqual(Enum.foo, @import("zon/foo.zon")); + try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); } test "int" { From a373aa1da9856d7f388d83788f380deb52972053 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 12:38:31 -0800 Subject: [PATCH 05/51] Starts moving parsing into known result type --- src/Sema.zig | 14 ++-- src/zon.zig | 146 +++++++++++++++++++++++++++--------------- test/behavior/zon.zig | 27 ++++---- 3 files changed, 116 insertions(+), 71 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 58dcb2b851b9..1cf7d8b1b5a4 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14421,8 +14421,6 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const operand_src = block.tokenOffset(inst_data.src_tok); const operand = sema.code.nullTerminatedString(extra.path); - _ = extra.res_ty; - const result = pt.importFile(block.getFileScope(zcu), operand) catch |err| switch (err) { error.ImportOutsideModulePath => { return sema.fail(block, operand_src, "import of file outside module path: '{s}'", .{operand}); @@ -14453,8 +14451,16 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. // 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) }); }; - const res_ty_inst = try sema.resolveInstAllowNone(extra.res_ty); - const res_ty = res_ty_inst.toTypeAllowNone(); + + if (extra.res_ty == .none) { + return sema.fail(block, operand_src, "import 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 ZON must have a known result type", .{}); + } + const interned = try zon.lower( sema, result.file, diff --git a/src/zon.zig b/src/zon.zig index 2a2cf33f36f3..e902d5ec1fbe 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -21,13 +21,12 @@ sema: *Sema, file: *File, file_index: Zcu.File.Index, -/// Lowers the given file as ZON. `res_ty` is a hint that's used to add indirection as needed to -/// match the result type, actual type checking is not done until assignment. +/// Lowers the given file as ZON. pub fn lower( sema: *Sema, file: *File, file_index: Zcu.File.Index, - res_ty: ?Type, + res_ty: Type, ) CompileError!InternPool.Index { const lower_zon: LowerZon = .{ .sema = sema, @@ -242,43 +241,97 @@ const FieldTypes = union(enum) { } }; -fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { +fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const gpa = self.sema.gpa; const ip = &self.sema.pt.zcu.intern_pool; const data = self.file.tree.nodes.items(.data); const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); + // Implement this! + switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { + .void => { + if (tags[node] == .block_two and data[node].lhs == 0 and data[node].rhs == 0) { + return .void_value; + } + + return self.fail(.{ .node_abs = node }, "expected void", .{}); + }, + .bool => { + if (tags[node] == .identifier) { + const token = main_tokens[node]; + var litIdent = try self.ident(token); + defer litIdent.deinit(gpa); + + const BoolIdent = enum { true, false }; + const values = std.StaticStringMap(BoolIdent).initComptime(.{ + .{ "true", .true }, + .{ "false", .false }, + }); + if (values.get(litIdent.bytes)) |value| { + return switch (value) { + .true => .bool_true, + .false => .bool_false, + }; + } + return self.fail(.{ .node_abs = node }, "unexpected identifier '{s}'", .{litIdent.bytes}); + } + return self.fail(.{ .node_abs = node }, "expected bool", .{}); + }, + .int, .comptime_int => {}, + .float, .comptime_float => {}, + .pointer => {}, + .optional => {}, + .@"enum" => {}, + .@"union" => {}, + .null => {}, + .enum_literal => {}, + .@"struct" => {}, + .array => {}, + + .type, + .noreturn, + .undefined, + .error_union, + .error_set, + .@"fn", + .@"opaque", + .frame, + .@"anyframe", + .vector, + => { + @panic("unimplemented"); + }, + } + // If the result type is slice, and our AST Node is not a slice, recurse and then take the // address of the result so attempt to coerce it into a slice. - if (res_ty) |rt| { - const result_is_slice = rt.isSlice(self.sema.pt.zcu); - const ast_is_pointer = switch (tags[node]) { - .string_literal, .multiline_string_literal => true, - else => false, - }; - if (result_is_slice and !ast_is_pointer) { - const val = try self.expr(node, rt.childType(self.sema.pt.zcu)); - const val_type = ip.typeOf(val); - const ptr_type = try self.sema.pt.ptrTypeSema(.{ - .child = val_type, - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - }); - _ = ptr_type; - @panic("unimplemented"); - // return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ - // .ty = ptr_type.toIntern(), - // .base_addr = .{ .anon_decl = .{ - // .orig_ty = ptr_type.toIntern(), - // .val = val, - // } }, - // .byte_offset = 0, - // } }); - } + const result_is_slice = res_ty.isSlice(self.sema.pt.zcu); + const ast_is_pointer = switch (tags[node]) { + .string_literal, .multiline_string_literal => true, + else => false, + }; + if (result_is_slice and !ast_is_pointer) { + const val = try self.expr(node, res_ty.childType(self.sema.pt.zcu)); + const val_type = ip.typeOf(val); + const ptr_type = try self.sema.pt.ptrTypeSema(.{ + .child = val_type, + .flags = .{ + .alignment = .none, + .is_const = true, + .address_space = .generic, + }, + }); + _ = ptr_type; + @panic("unimplemented"); + // return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ + // .ty = ptr_type.toIntern(), + // .base_addr = .{ .anon_decl = .{ + // .orig_ty = ptr_type.toIntern(), + // .val = val, + // } }, + // .byte_offset = 0, + // } }); } switch (tags[node]) { @@ -287,18 +340,14 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { var litIdent = try self.ident(token); defer litIdent.deinit(gpa); - const LitIdent = enum { true, false, null, nan, inf }; + const LitIdent = enum { null, nan, inf }; const values = std.StaticStringMap(LitIdent).initComptime(.{ - .{ "true", .true }, - .{ "false", .false }, .{ "null", .null }, .{ "nan", .nan }, .{ "inf", .inf }, }); if (values.get(litIdent.bytes)) |value| { return switch (value) { - .true => .bool_true, - .false => .bool_false, .null => .null_value, .nan => self.sema.pt.intern(.{ .float = .{ .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), @@ -424,7 +473,7 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { return self.fail(.{ .token_abs = name_token }, "duplicate field", .{}); } - const elem_ty = rt_field_types.get(name, self.sema.pt.zcu); + const elem_ty = rt_field_types.get(name, self.sema.pt.zcu) orelse @panic("unimplemented"); values[i] = try self.expr(field, elem_ty); types[i] = ip.typeOf(values[i]); @@ -460,19 +509,19 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { const values = try gpa.alloc(InternPool.Index, array_init.ast.elements.len); defer gpa.free(values); for (array_init.ast.elements, 0..) |elem, i| { - const elem_ty = if (res_ty) |rt| b: { - const type_tag = rt.zigTypeTagOrPoison(self.sema.pt.zcu) catch break :b null; + const elem_ty = b: { + const type_tag = res_ty.zigTypeTagOrPoison(self.sema.pt.zcu) catch break :b null; switch (type_tag) { - .array => break :b rt.childType(self.sema.pt.zcu), + .array => break :b res_ty.childType(self.sema.pt.zcu), .@"struct" => { - try rt.resolveFully(self.sema.pt); - if (i >= rt.structFieldCount(self.sema.pt.zcu)) break :b null; - break :b rt.fieldType(i, self.sema.pt.zcu); + try res_ty.resolveFully(self.sema.pt); + if (i >= res_ty.structFieldCount(self.sema.pt.zcu)) break :b null; + break :b res_ty.fieldType(i, self.sema.pt.zcu); }, else => break :b null, } - } else null; - values[i] = try self.expr(elem, elem_ty); + }; + values[i] = try self.expr(elem, elem_ty orelse @panic("unimplemented")); types[i] = ip.typeOf(values[i]); } @@ -487,11 +536,6 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: ?Type) !InternPool.Index { // .storage = .{ .elems = values }, // } }); }, - .block_two => if (data[node].lhs == 0 and data[node].rhs == 0) { - return .void_value; - } else { - return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); - }, else => {}, } diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index b10396a53c38..2a8609855102 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -7,26 +7,21 @@ const expectEqualSlices = std.testing.expectEqualSlices; const expectEqualStrings = std.testing.expectEqualStrings; test "void" { - try expectEqual({}, @import("zon/void.zon")); + try expectEqual({}, @as(void, @import("zon/void.zon"))); } test "bool" { - try expectEqual(true, @import("zon/true.zon")); - try expectEqual(false, @import("zon/false.zon")); + try expectEqual(true, @as(bool, @import("zon/true.zon"))); + try expectEqual(false, @as(bool, @import("zon/false.zon"))); } test "optional" { - // Coercion const some: ?u32 = @import("zon/some.zon"); const none: ?u32 = @import("zon/none.zon"); const @"null": @TypeOf(null) = @import("zon/none.zon"); - try expectEqual(some, 10); - try expectEqual(none, null); - try expectEqual(@"null", null); - - // No coercion - try expectEqual(some, @import("zon/some.zon")); - try expectEqual(none, @import("zon/none.zon")); + try expectEqual(@as(u32, 10), some); + try expectEqual(@as(?u32, null), none); + try expectEqual(null, @"null"); } test "union" { @@ -78,9 +73,9 @@ test "tuple" { } test "char" { - try expectEqual('a', @import("zon/a.zon")); - try expectEqual('z', @import("zon/z.zon")); - try expectEqual(-'a', @import("zon/a_neg.zon")); + try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon"))); + try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon"))); + try expectEqual(@as(i8, -'a'), @as(i8, @import("zon/a_neg.zon"))); } test "arrays" { @@ -158,8 +153,8 @@ test "enum literals" { baz, @"0\na", }; - try expectEqual(Enum.foo, @import("zon/foo.zon")); - try expectEqual(Enum.@"0\na", @import("zon/escaped_enum.zon")); + try expectEqual(Enum.foo, @as(Enum, @import("zon/foo.zon"))); + try expectEqual(Enum.@"0\na", @as(Enum, @import("zon/escaped_enum.zon"))); } test "int" { From f16ea4abedebfe998f3d4884057504b7e2803f84 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 13:33:05 -0800 Subject: [PATCH 06/51] Starts moving optionals to known result type parsing Need to do ints next --- src/zon.zig | 28 ++++++++++++++++++++++------ test/behavior/zon.zig | 7 +++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index e902d5ec1fbe..a1c3bd7e5b48 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -274,17 +274,35 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { .false => .bool_false, }; } - return self.fail(.{ .node_abs = node }, "unexpected identifier '{s}'", .{litIdent.bytes}); } return self.fail(.{ .node_abs = node }, "expected bool", .{}); }, .int, .comptime_int => {}, .float, .comptime_float => {}, .pointer => {}, - .optional => {}, + .optional => { + if (tags[node] == .identifier) { + const token = main_tokens[node]; + const bytes = self.file.tree.tokenSlice(token); + if (std.mem.eql(u8, bytes, "null")) return .null_value; + } + + // XXX: this fails, assert(opt.val == .none or ip.indexToKey(opt.ty).opt_type == ip.typeOf(opt.val)); + // XXX: i think its casue ints arent returned as correct type try on bool to test that theory + return self.sema.pt.intern(.{ .opt = .{ + .ty = res_ty.toIntern(), + .val = try self.expr(node, res_ty.optionalChild(self.sema.pt.zcu)), + } }); + }, .@"enum" => {}, .@"union" => {}, - .null => {}, + .null => { + if (tags[node] == .identifier) { + const token = main_tokens[node]; + const bytes = self.file.tree.tokenSlice(token); + if (std.mem.eql(u8, bytes, "null")) return .null_value; + } + }, .enum_literal => {}, .@"struct" => {}, .array => {}, @@ -340,15 +358,13 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { var litIdent = try self.ident(token); defer litIdent.deinit(gpa); - const LitIdent = enum { null, nan, inf }; + const LitIdent = enum { nan, inf }; const values = std.StaticStringMap(LitIdent).initComptime(.{ - .{ "null", .null }, .{ "nan", .nan }, .{ "inf", .inf }, }); if (values.get(litIdent.bytes)) |value| { return switch (value) { - .null => .null_value, .nan => self.sema.pt.intern(.{ .float = .{ .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), .storage = .{ .f128 = std.math.nan(f128) }, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 2a8609855102..4cf847e661fd 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -16,12 +16,15 @@ test "bool" { } test "optional" { - const some: ?u32 = @import("zon/some.zon"); + // 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, 10), some); try expectEqual(@as(?u32, null), none); try expectEqual(null, @"null"); + // We still need to uncomment the some value tests, to do that we need to fix integer parsing to + // return the correct result types + return error.SkipZigTest; } test "union" { From cc7f19a4c423b9cfa56da1fd5446d8900088f168 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 17:00:40 -0800 Subject: [PATCH 07/51] Parses integers to known result types. Not yet tested as those tests use structs. --- src/zon.zig | 197 +++++++++++++++++++++++++++++++++++------- test/behavior/zon.zig | 7 +- 2 files changed, 167 insertions(+), 37 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index a1c3bd7e5b48..0a6f429f4a58 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -277,8 +277,7 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { } return self.fail(.{ .node_abs = node }, "expected bool", .{}); }, - .int, .comptime_int => {}, - .float, .comptime_float => {}, + .int, .comptime_int, .float, .comptime_float => return self.number(node, res_ty), .pointer => {}, .optional => { if (tags[node] == .identifier) { @@ -287,8 +286,6 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { if (std.mem.eql(u8, bytes, "null")) return .null_value; } - // XXX: this fails, assert(opt.val == .none or ip.indexToKey(opt.ty).opt_type == ip.typeOf(opt.val)); - // XXX: i think its casue ints arent returned as correct type try on bool to test that theory return self.sema.pt.intern(.{ .opt = .{ .ty = res_ty.toIntern(), .val = try self.expr(node, res_ty.optionalChild(self.sema.pt.zcu)), @@ -377,8 +374,6 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { } return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{litIdent.bytes}); }, - .number_literal, .char_literal => return self.number(node, null), - .negation => return self.number(data[node].lhs, node), .enum_literal => return ip.get(gpa, self.sema.pt.tid, .{ .enum_literal = try self.identAsNullTerminatedString(main_tokens[node]), }), @@ -558,13 +553,29 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } -fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !InternPool.Index { +fn number( + self: LowerZon, + node: Ast.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + const gpa = self.sema.gpa; const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); - switch (tags[node]) { + const num_lit_node, const is_negative = if (tags[node] == .negation) b: { + const data = self.file.tree.nodes.items(.data); + break :b .{ + data[node].lhs, + node, + }; + } else .{ + node, + null, + }; + switch (tags[num_lit_node]) { .char_literal => { - const token = main_tokens[node]; + const token = main_tokens[num_lit_node]; const token_bytes = self.file.tree.tokenSlice(token); const char = switch (std.zig.string_literal.parseCharLiteral(token_bytes)) { .success => |char| char, @@ -577,13 +588,15 @@ fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !I ); }, }; - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_int }), - .storage = .{ .i64 = if (is_negative == null) char else -@as(i64, char) }, - } }); + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = if (is_negative == null) char else -@as(i64, char) }, + }, + }); }, .number_literal => { - const token = main_tokens[node]; + const token = main_tokens[num_lit_node]; const token_bytes = self.file.tree.tokenSlice(token); const parsed = std.zig.number_literal.parseNumberLiteral(token_bytes); switch (parsed) { @@ -596,18 +609,58 @@ fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !I var result = try std.math.big.int.Managed.initSet(gpa, unsigned); defer result.deinit(); result.negate(); + + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!result.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + .{ .node_abs = num_lit_node }, + "type '{}' cannot represent integer value '-{}'", + .{ res_ty.fmt(self.sema.pt), unsigned }, + ); + } + } + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = .comptime_int_type, + .ty = res_ty.toIntern(), .storage = .{ .big_int = result.toConst() }, } }); }; + + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (std.math.cast(u6, int_info.bits)) |bits| { + const min_int: i64 = if (int_info.signedness == .unsigned) 0 else -(@as(i64, 1) << (bits - 1)); + if (signed < min_int) { + return self.fail( + .{ .node_abs = num_lit_node }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), unsigned }, + ); + } + } + } + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = .comptime_int_type, + .ty = res_ty.toIntern(), .storage = .{ .i64 = signed }, } }); } else { + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (std.math.cast(u6, int_info.bits)) |bits| { + const max_int: u64 = (@as(u64, 1) << (bits - @intFromBool(int_info.signedness == .signed))) - 1; + if (unsigned > max_int) { + return self.fail( + .{ .node_abs = num_lit_node }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), unsigned }, + ); + } + } + } return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = .comptime_int_type, + .ty = res_ty.toIntern(), .storage = .{ .u64 = unsigned }, } }); } @@ -627,24 +680,92 @@ fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !I if (is_negative != null) big_int.negate(); + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!big_int.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + .{ .node_abs = num_lit_node }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), big_int }, + ); + } + } + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_int }), + .ty = res_ty.toIntern(), .storage = .{ .big_int = big_int.toConst() }, } }); }, .float => { - const unsigned_float = std.fmt.parseFloat(f128, token_bytes) catch unreachable; // Already validated + const unsigned_float = std.fmt.parseFloat(f128, token_bytes) catch { + // Validated by tokenizer + unreachable; + }; const float = if (is_negative == null) unsigned_float else -unsigned_float; - return self.sema.pt.intern(.{ .float = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), - .storage = .{ .f128 = float }, - } }); + switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { + .float, .comptime_float => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { + 16 => .{ .f16 = @floatCast(float) }, + 32 => .{ .f32 = @floatCast(float) }, + 64 => .{ .f64 = @floatCast(float) }, + 80 => .{ .f80 = @floatCast(float) }, + 128 => .{ .f128 = float }, + else => unreachable, + }, + } }), + .int, .comptime_int => { + // Check for fractional components + if (@rem(float, 1) != 0) { + return self.fail( + .{ .node_abs = num_lit_node }, + "fractional component prevents float value '{}' from coercion to type '{}'", + .{ float, res_ty.fmt(self.sema.pt) }, + ); + } + + // Create a rational representation of the float + var rational = try std.math.big.Rational.init(gpa); + defer rational.deinit(); + rational.setFloat(f128, float) 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)); + if (is_negative != null) rational.negate(); + + // Check that the result is in range of the result type + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + .{ .node_abs = num_lit_node }, + "float value '{}' cannot be stored in integer type '{}'", + .{ float, res_ty.fmt(self.sema.pt) }, + ); + } + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = rational.p.toConst() }, + }, + }); + }, + else => unreachable, + } }, .failure => |err| return self.failWithNumberError(token, err), } }, .identifier => { - const token = main_tokens[node]; + switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { + .float, .comptime_float => {}, + else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), + } + const token = main_tokens[num_lit_node]; const bytes = self.file.tree.tokenSlice(token); const LitIdent = enum { nan, inf }; const values = std.StaticStringMap(LitIdent).initComptime(.{ @@ -654,20 +775,32 @@ fn number(self: LowerZon, node: Ast.Node.Index, is_negative: ?Ast.Node.Index) !I if (values.get(bytes)) |value| { return switch (value) { .nan => self.sema.pt.intern(.{ .float = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), - .storage = .{ .f128 = std.math.nan(f128) }, + .ty = res_ty.toIntern(), + .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { + 16 => .{ .f16 = std.math.nan(f16) }, + 32 => .{ .f32 = std.math.nan(f32) }, + 64 => .{ .f64 = std.math.nan(f64) }, + 80 => .{ .f80 = std.math.nan(f80) }, + 128 => .{ .f128 = std.math.nan(f128) }, + else => unreachable, + }, } }), - .inf => try self.sema.pt.intern(.{ .float = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), - .storage = .{ - .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128), + .inf => self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { + 16 => .{ .f16 = if (is_negative == null) std.math.inf(f16) else -std.math.inf(f16) }, + 32 => .{ .f32 = if (is_negative == null) std.math.inf(f32) else -std.math.inf(f32) }, + 64 => .{ .f64 = if (is_negative == null) std.math.inf(f64) else -std.math.inf(f64) }, + 80 => .{ .f80 = if (is_negative == null) std.math.inf(f80) else -std.math.inf(f80) }, + 128 => .{ .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128) }, + else => unreachable, }, } }), }; } - return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{bytes}); + return self.fail(.{ .node_abs = num_lit_node }, "use of unknown identifier '{s}'", .{bytes}); }, - else => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), + else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), } } diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 4cf847e661fd..2a8609855102 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -16,15 +16,12 @@ test "bool" { } test "optional" { - // const some: ?u32 = @import("zon/some.zon"); + 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, 10), some); try expectEqual(@as(?u32, null), none); try expectEqual(null, @"null"); - // We still need to uncomment the some value tests, to do that we need to fix integer parsing to - // return the correct result types - return error.SkipZigTest; } test "union" { From a4b959603858b447bfac2d54bc426a86a8cda271 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 20:12:43 -0800 Subject: [PATCH 08/51] Pulls out separate types into their own functions to make easier to jump around in the code --- src/zon.zig | 130 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 54 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 0a6f429f4a58..d23b055a6adf 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -40,7 +40,7 @@ pub fn lower( const data = tree.nodes.items(.data); const root = data[0].lhs; - return lower_zon.expr(root, res_ty); + return lower_zon.parseExpr(root, res_ty); } fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { @@ -241,65 +241,22 @@ const FieldTypes = union(enum) { } }; -fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!InternPool.Index { const gpa = self.sema.gpa; const ip = &self.sema.pt.zcu.intern_pool; const data = self.file.tree.nodes.items(.data); const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); - // Implement this! switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { - .void => { - if (tags[node] == .block_two and data[node].lhs == 0 and data[node].rhs == 0) { - return .void_value; - } - - return self.fail(.{ .node_abs = node }, "expected void", .{}); - }, - .bool => { - if (tags[node] == .identifier) { - const token = main_tokens[node]; - var litIdent = try self.ident(token); - defer litIdent.deinit(gpa); - - const BoolIdent = enum { true, false }; - const values = std.StaticStringMap(BoolIdent).initComptime(.{ - .{ "true", .true }, - .{ "false", .false }, - }); - if (values.get(litIdent.bytes)) |value| { - return switch (value) { - .true => .bool_true, - .false => .bool_false, - }; - } - } - return self.fail(.{ .node_abs = node }, "expected bool", .{}); - }, - .int, .comptime_int, .float, .comptime_float => return self.number(node, res_ty), + .void => return self.parseVoid(node), + .bool => return self.parseBool(node), + .int, .comptime_int, .float, .comptime_float => return self.parseNumber(node, res_ty), .pointer => {}, - .optional => { - if (tags[node] == .identifier) { - const token = main_tokens[node]; - const bytes = self.file.tree.tokenSlice(token); - if (std.mem.eql(u8, bytes, "null")) return .null_value; - } - - return self.sema.pt.intern(.{ .opt = .{ - .ty = res_ty.toIntern(), - .val = try self.expr(node, res_ty.optionalChild(self.sema.pt.zcu)), - } }); - }, + .optional => return self.parseOptional(node, res_ty), .@"enum" => {}, .@"union" => {}, - .null => { - if (tags[node] == .identifier) { - const token = main_tokens[node]; - const bytes = self.file.tree.tokenSlice(token); - if (std.mem.eql(u8, bytes, "null")) return .null_value; - } - }, + .null => return self.parseNull(node), .enum_literal => {}, .@"struct" => {}, .array => {}, @@ -327,7 +284,7 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { else => false, }; if (result_is_slice and !ast_is_pointer) { - const val = try self.expr(node, res_ty.childType(self.sema.pt.zcu)); + const val = try self.parseExpr(node, res_ty.childType(self.sema.pt.zcu)); const val_type = ip.typeOf(val); const ptr_type = try self.sema.pt.ptrTypeSema(.{ .child = val_type, @@ -486,7 +443,7 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const elem_ty = rt_field_types.get(name, self.sema.pt.zcu) orelse @panic("unimplemented"); - values[i] = try self.expr(field, elem_ty); + values[i] = try self.parseExpr(field, elem_ty); types[i] = ip.typeOf(values[i]); } @@ -532,7 +489,7 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { else => break :b null, } }; - values[i] = try self.expr(elem, elem_ty orelse @panic("unimplemented")); + values[i] = try self.parseExpr(elem, elem_ty orelse @panic("unimplemented")); types[i] = ip.typeOf(values[i]); } @@ -553,7 +510,43 @@ fn expr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } -fn number( +fn parseVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { + const tags = self.file.tree.nodes.items(.tag); + const data = self.file.tree.nodes.items(.data); + + if (tags[node] == .block_two and data[node].lhs == 0 and data[node].rhs == 0) { + return .void_value; + } + + return self.fail(.{ .node_abs = node }, "expected void", .{}); +} + +fn parseBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { + const gpa = self.sema.gpa; + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + + if (tags[node] == .identifier) { + const token = main_tokens[node]; + var litIdent = try self.ident(token); + defer litIdent.deinit(gpa); + + const BoolIdent = enum { true, false }; + const values = std.StaticStringMap(BoolIdent).initComptime(.{ + .{ "true", .true }, + .{ "false", .false }, + }); + if (values.get(litIdent.bytes)) |value| { + return switch (value) { + .true => .bool_true, + .false => .bool_false, + }; + } + } + return self.fail(.{ .node_abs = node }, "expected bool", .{}); +} + +fn parseNumber( self: LowerZon, node: Ast.Node.Index, res_ty: Type, @@ -804,6 +797,35 @@ fn number( } } +fn parseOptional(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + + if (tags[node] == .identifier) { + const token = main_tokens[node]; + const bytes = self.file.tree.tokenSlice(token); + if (std.mem.eql(u8, bytes, "null")) return .null_value; + } + + return self.sema.pt.intern(.{ .opt = .{ + .ty = res_ty.toIntern(), + .val = try self.parseExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), + } }); +} + +fn parseNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + + if (tags[node] == .identifier) { + const token = main_tokens[node]; + const bytes = self.file.tree.tokenSlice(token); + if (std.mem.eql(u8, bytes, "null")) return .null_value; + } + + return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); +} + fn createErrorWithOptionalNote( self: LowerZon, src_loc: LazySrcLoc, From 78a52d774216dcc328dde398ee7bb138ed0a9665 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 01:47:08 -0800 Subject: [PATCH 09/51] Parses arrays to known result type --- lib/std/zon/parse.zig | 1 - src/zon.zig | 56 ++++++++++++++++++++++++++++++++++++++++++- test/behavior/zon.zig | 10 ++++---- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 3a75140502e2..ecc783994ff6 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -883,7 +883,6 @@ fn elements( } } - // Fail return self.failExpectedContainer(T, main_tokens[node]); } diff --git a/src/zon.zig b/src/zon.zig index d23b055a6adf..67877ef6bf38 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -14,6 +14,7 @@ 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 LowerZon = @This(); @@ -259,7 +260,7 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .null => return self.parseNull(node), .enum_literal => {}, .@"struct" => {}, - .array => {}, + .array => return self.parseArray(node, res_ty), .type, .noreturn, @@ -826,6 +827,59 @@ fn parseNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } +fn parseArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const gpa = self.sema.gpa; + + const array_info = res_ty.arrayInfo(self.sema.pt.zcu); + var buf: [2]NodeIndex = undefined; + const elem_nodes = try self.elements(res_ty, &buf, node); + + if (elem_nodes.len != array_info.len) { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + } + + const elems = try gpa.alloc(InternPool.Index, array_info.len + @intFromBool(array_info.sentinel != null)); + defer gpa.free(elems); + + for (elem_nodes, 0..) |elem_node, i| { + elems[i] = try self.parseExpr(elem_node, 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 elements( + self: LowerZon, + container: Type, + buf: *[2]NodeIndex, + node: NodeIndex, +) ![]const NodeIndex { + if (self.file.tree.fullArrayInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + } + return init.ast.elements; + } + + if (self.file.tree.fullStructInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + } + if (init.ast.fields.len == 0) { + return init.ast.fields; + } + } + + return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); +} + fn createErrorWithOptionalNote( self: LowerZon, src_loc: LazySrcLoc, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 2a8609855102..85cf8a632e15 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -79,10 +79,12 @@ test "char" { } test "arrays" { - return error.SkipZigTest; - // try expectEqual([0]u8{}, @import("zon/vec0.zon")); - // try expectEqual([4]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); - // try expectEqual([4:2]u8{ 'a', 'b', 'c', 'd' }, @import("zon/array.zon")); + 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" { From 28c8f32c4004444d46c5acf7e4586cd2396c2237 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 13:10:32 -0800 Subject: [PATCH 10/51] Parses enums to known result type --- src/zon.zig | 44 ++++++++++++++++++++++++++++++++++++++----- test/behavior/zon.zig | 1 + 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 67877ef6bf38..3d1c95d3ca8e 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -255,10 +255,10 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .int, .comptime_int, .float, .comptime_float => return self.parseNumber(node, res_ty), .pointer => {}, .optional => return self.parseOptional(node, res_ty), - .@"enum" => {}, .@"union" => {}, .null => return self.parseNull(node), - .enum_literal => {}, + .@"enum" => return self.parseEnum(node, res_ty), + .enum_literal => return self.parseEnumLiteral(node, res_ty), .@"struct" => {}, .array => return self.parseArray(node, res_ty), @@ -332,9 +332,6 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In } return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{litIdent.bytes}); }, - .enum_literal => return ip.get(gpa, self.sema.pt.tid, .{ - .enum_literal = try self.identAsNullTerminatedString(main_tokens[node]), - }), .string_literal => { const token = main_tokens[node]; const raw_string = self.file.tree.tokenSlice(token); @@ -855,6 +852,43 @@ fn parseArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } +fn parseEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const main_tokens = self.file.tree.nodes.items(.main_token); + const tags = self.file.tree.nodes.items(.tag); + const ip = &self.sema.pt.zcu.intern_pool; + + if (tags[node] != .enum_literal) { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + } + + const field_name = try self.identAsNullTerminatedString(main_tokens[node]); + const field_index = res_ty.enumFieldIndex(field_name, self.sema.pt.zcu) orelse { + return self.fail(.{ .node_abs = node }, "enum {} has no member named '{}'", .{ + res_ty.fmt(self.sema.pt), + field_name.fmt(ip), + }); + }; + + const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index); + + return value.toIntern(); +} + +fn parseEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const main_tokens = self.file.tree.nodes.items(.main_token); + const tags = self.file.tree.nodes.items(.tag); + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + + if (tags[node] != .enum_literal) { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + } + + return ip.get(gpa, self.sema.pt.tid, .{ + .enum_literal = try self.identAsNullTerminatedString(main_tokens[node]), + }); +} + fn elements( self: LowerZon, container: Type, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 85cf8a632e15..221cbff938bb 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -156,6 +156,7 @@ test "enum literals" { @"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"))); } From 57d6cd599e13b9a09bd8ea6b0a09590cb022f18f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 14:13:58 -0800 Subject: [PATCH 11/51] WIP tuples: crash on string literals right now --- src/zon.zig | 40 +++++++++++++++++++++++++++++++++++++--- test/behavior/zon.zig | 5 +++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 3d1c95d3ca8e..34cdce49c52c 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -253,14 +253,14 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .void => return self.parseVoid(node), .bool => return self.parseBool(node), .int, .comptime_int, .float, .comptime_float => return self.parseNumber(node, res_ty), - .pointer => {}, .optional => return self.parseOptional(node, res_ty), - .@"union" => {}, .null => return self.parseNull(node), .@"enum" => return self.parseEnum(node, res_ty), .enum_literal => return self.parseEnumLiteral(node, res_ty), - .@"struct" => {}, .array => return self.parseArray(node, res_ty), + .@"struct" => return self.parseStruct(node, res_ty), + .@"union" => {}, + .pointer => {}, .type, .noreturn, @@ -889,6 +889,40 @@ fn parseEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternP }); } +fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + + // XXX: actually check if is tuple vs struct, probably from outsider so we can make separate functions + const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; + + var buf: [2]Ast.Node.Index = undefined; + const elem_nodes = try self.elements(res_ty, &buf, node); + + // XXX: keep in mind that default fields are allowed *if comptime*, also make sure packed types work correctly + const field_types = tuple_info.types.get(ip); + if (elem_nodes.len < field_types.len) { + return self.fail(.{ .node_abs = node }, "missing tuple field with index {}", .{elem_nodes.len}); + } else if (elem_nodes.len > field_types.len) { + return self.fail(.{ .node_abs = node }, "index {} outside tuple of length {}", .{ + field_types.len, + elem_nodes[field_types.len], + }); + } + + const elems = try gpa.alloc(InternPool.Index, field_types.len); + defer gpa.free(elems); + + for (elems, elem_nodes, field_types) |*elem, elem_node, field_type| { + elem.* = try self.parseExpr(elem_node, Type.fromInterned(field_type)); + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ .elems = elems }, + } }); +} + fn elements( self: LowerZon, container: Type, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 221cbff938bb..808e42dee24d 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -68,8 +68,9 @@ test "struct enum field" { } test "tuple" { - return error.SkipZigTest; - // try expectEqualDeep(.{ 1.2, true, "hello", 3 }, @import("zon/tuple.zon")); + // XXX: this is failing because string literals don't match types here for some reason + const Tuple = struct { f32, bool, []const u8, u16 }; + try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); } test "char" { From f28d829f86f6f204d3fe71a5b68f99ca7409258c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 14:29:24 -0800 Subject: [PATCH 12/51] Parses tuples to known result types --- src/zon.zig | 15 +++++++++++---- test/behavior/zon.zig | 6 +++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 34cdce49c52c..7f2e32df84d0 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -258,7 +258,7 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .@"enum" => return self.parseEnum(node, res_ty), .enum_literal => return self.parseEnumLiteral(node, res_ty), .array => return self.parseArray(node, res_ty), - .@"struct" => return self.parseStruct(node, res_ty), + .@"struct" => return self.parseStructOrTuple(node, res_ty), .@"union" => {}, .pointer => {}, @@ -889,17 +889,24 @@ fn parseEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternP }); } -fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn parseStructOrTuple(self: LowerZon, node: Ast.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.parseTuple(node, res_ty), + .struct_type => @panic("unimplemented"), + else => self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}), + }; +} + +fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; - // XXX: actually check if is tuple vs struct, probably from outsider so we can make separate functions const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; var buf: [2]Ast.Node.Index = undefined; const elem_nodes = try self.elements(res_ty, &buf, node); - // XXX: keep in mind that default fields are allowed *if comptime*, also make sure packed types work correctly const field_types = tuple_info.types.get(ip); if (elem_nodes.len < field_types.len) { return self.fail(.{ .node_abs = node }, "missing tuple field with index {}", .{elem_nodes.len}); diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 808e42dee24d..df8504d0640c 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -68,9 +68,9 @@ test "struct enum field" { } test "tuple" { - // XXX: this is failing because string literals don't match types here for some reason - const Tuple = struct { f32, bool, []const u8, u16 }; - try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); + return error.SkipZigTest; // Failing because we haven't updated string parsing yet + // const Tuple = struct { f32, bool, []const u8, u16 }; + // try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); } test "char" { From 816ce90a9a0021897fd0545896f959923904e7ee Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 15:28:59 -0800 Subject: [PATCH 13/51] Parses strings to known result type --- src/zon.zig | 83 ++++++++++++++++++++++++++++++++++++++++++- test/behavior/zon.zig | 6 ++-- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 7f2e32df84d0..ecfd564c32a5 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -260,7 +260,7 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .array => return self.parseArray(node, res_ty), .@"struct" => return self.parseStructOrTuple(node, res_ty), .@"union" => {}, - .pointer => {}, + .pointer => return self.parsePointer(node, res_ty), .type, .noreturn, @@ -930,6 +930,87 @@ fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } +fn parsePointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const tags = self.file.tree.nodes.items(.tag); + + const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); + + if (ptr_info.flags.size != .Slice) { + return self.fail(.{ .node_abs = node }, "ZON import cannot be coerced to non slice pointer", .{}); + } + + 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) { + if (tags[node] == .string_literal or tags[node] == .multiline_string_literal) { + return self.parseStringLiteral(node, res_ty); + } + } + + var buf: [2]Ast.Node.Index = undefined; + const elem_nodes = try self.elements(res_ty, &buf, node); + _ = elem_nodes; + @panic("unimplemented"); +} + +fn parseStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const gpa = self.sema.gpa; + const ip = &self.sema.pt.zcu.intern_pool; + const main_tokens = self.file.tree.nodes.items(.main_token); + const tags = self.file.tree.nodes.items(.tag); + const data = self.file.tree.nodes.items(.data); + + const token = main_tokens[node]; + const raw_string = self.file.tree.tokenSlice(token); + + var bytes = std.ArrayListUnmanaged(u8){}; + defer bytes.deinit(gpa); + switch (tags[node]) { + .string_literal => switch (try std.zig.string_literal.parseWrite(bytes.writer(gpa), raw_string)) { + .success => {}, + .failure => |err| { + const offset = self.file.tree.tokens.items(.start)[token]; + return self.fail( + .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, + "{}", + .{err.fmtWithSource(raw_string)}, + ); + }, + }, + .multiline_string_literal => { + var parser = std.zig.string_literal.multilineParser(bytes.writer(gpa)); + var tok_i = data[node].lhs; + while (tok_i <= data[node].rhs) : (tok_i += 1) { + try parser.line(self.file.tree.tokenSlice(tok_i)); + } + }, + else => unreachable, + } + + const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ + .len = bytes.items.len, + .sentinel = .zero_u8, + .child = .u8_type, + } }); + const array_val = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty, + .storage = .{ .bytes = string }, + } }); + return self.sema.pt.intern(.{ .slice = .{ + .ty = res_ty.toIntern(), + .ptr = try self.sema.pt.intern(.{ .ptr = .{ + .ty = .manyptr_const_u8_sentinel_0_type, + .base_addr = .{ .uav = .{ + .orig_ty = .slice_const_u8_sentinel_0_type, + .val = array_val, + } }, + .byte_offset = 0, + } }), + .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), + } }); +} + fn elements( self: LowerZon, container: Type, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index df8504d0640c..0b662da1db71 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -68,9 +68,8 @@ test "struct enum field" { } test "tuple" { - return error.SkipZigTest; // Failing because we haven't updated string parsing yet - // const Tuple = struct { f32, bool, []const u8, u16 }; - // try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); + const Tuple = struct { f32, bool, []const u8, u16 }; + try expectEqualDeep(Tuple{ 1.2, true, "hello", 3 }, @as(Tuple, @import("zon/tuple.zon"))); } test "char" { @@ -141,6 +140,7 @@ test "string literals" { 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! From 0727981eb905c53e39cf5b10fb7a809fbacf3b38 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 11 Nov 2024 16:48:30 -0800 Subject: [PATCH 14/51] Parses unions to explicit return types Coercing enum literals to unions isn't yet implemented, and some of the union tests are still skipped due to a bug with explicit tags I still need to fix --- src/zon.zig | 78 ++++++++++++++++++++++++++++++++++++++++++- test/behavior/zon.zig | 55 +++++++++++++++++++++++++----- 2 files changed, 123 insertions(+), 10 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index ecfd564c32a5..61e889284ae8 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -259,7 +259,7 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .enum_literal => return self.parseEnumLiteral(node, res_ty), .array => return self.parseArray(node, res_ty), .@"struct" => return self.parseStructOrTuple(node, res_ty), - .@"union" => {}, + .@"union" => return self.parseUnion(node, res_ty), .pointer => return self.parsePointer(node, res_ty), .type, @@ -1011,6 +1011,82 @@ fn parseStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !Inter } }); } +fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const tags = self.file.tree.nodes.items(.tag); + const ip = &self.sema.pt.zcu.intern_pool; + + try res_ty.resolveFully(self.sema.pt); + const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?; + const enum_tag_info = union_info.loadTagType(ip); + + if (tags[node] == .enum_literal) @panic("unimplemented"); + + var buf: [2]Ast.Node.Index = undefined; + const field_nodes = try self.fields(res_ty, &buf, node); + if (field_nodes.len > 1) { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + } + const field_node = field_nodes[0]; + var field_name = try self.ident(self.file.tree.firstToken(field_node) - 2); + defer field_name.deinit(self.sema.gpa); + const field_name_string = try ip.getOrPutString( + self.sema.pt.zcu.gpa, + self.sema.pt.tid, + field_name.bytes, + .no_embedded_nulls, + ); + + const name_index = enum_tag_info.nameIndex(ip, field_name_string) orelse { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + }; + const tag = if (enum_tag_info.values.len == 0) b: { + // Fields are auto numbered + break :b try self.sema.pt.intern(.{ .enum_tag = .{ + .ty = union_info.enum_tag_ty, + .int = try self.sema.pt.intern(.{ .int = .{ + .ty = enum_tag_info.tag_ty, + .storage = .{ .u64 = name_index }, + } }), + } }); + } else b: { + // Fields are explicitly numbered + break :b enum_tag_info.values.get(ip)[name_index]; + }; + const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); + const val = try self.parseExpr(field_node, field_type); + return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ + .ty = res_ty.toIntern(), + .tag = tag, + .val = val, + }); +} + +fn fields( + self: LowerZon, + container: Type, + buf: *[2]NodeIndex, + node: NodeIndex, +) ![]const NodeIndex { + if (self.file.tree.fullStructInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + } + return init.ast.fields; + } + + if (self.file.tree.fullArrayInit(buf, node)) |init| { + if (init.ast.type_expr != 0) { + return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + } + if (init.ast.elements.len != 0) { + return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); + } + return init.ast.elements; + } + + return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); +} + fn elements( self: LowerZon, container: Type, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 0b662da1db71..f741a7d206db 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -25,17 +25,54 @@ test "optional" { } test "union" { + // No tag + { + const Union = union { + x: f32, + y: bool, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + + try expectEqual(union1.x, 1.5); + try expectEqual(union2.y, true); + } + + // Inferred tag + { + const Union = union(enum) { + x: f32, + y: bool, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + + try expectEqual(union1.x, 1.5); + try expectEqual(union2.y, true); + } + + // Skip because explicit tags aren't yet working as expected return error.SkipZigTest; - // const Union = union { - // x: f32, - // y: bool, - // }; - - // const union1: Union = @import("zon/union1.zon"); - // const union2: Union = @import("zon/union2.zon"); - // try expectEqual(union1.x, 1.5); - // try expectEqual(union2.y, true); + // Explicit tag + // { + // const Tag = enum(i128) { + // x = -1, + // y = 2, + // }; + // const Union = union(Tag) { + // x: f32, + // y: bool, + // }; + + // const union1: Union = @import("zon/union1.zon"); + // const union2: Union = @import("zon/union2.zon"); + + // try expectEqual(union1.x, 1.5); + // try expectEqual(union2.y, true); + // } } test "struct" { From 1d02d1fb1a8f4a8606b94b7f420d7bc472746846 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 11 Nov 2024 16:57:27 -0800 Subject: [PATCH 15/51] Fixes bug in explicit union tag parsing --- src/zon.zig | 15 ++++++++------- test/behavior/zon.zig | 35 ++++++++++++++++------------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 61e889284ae8..211400cb7af1 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -1039,19 +1039,20 @@ fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In const name_index = enum_tag_info.nameIndex(ip, field_name_string) orelse { return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); }; - const tag = if (enum_tag_info.values.len == 0) b: { + const tag_int = if (enum_tag_info.values.len == 0) b: { // Fields are auto numbered - break :b try self.sema.pt.intern(.{ .enum_tag = .{ - .ty = union_info.enum_tag_ty, - .int = try self.sema.pt.intern(.{ .int = .{ - .ty = enum_tag_info.tag_ty, - .storage = .{ .u64 = name_index }, - } }), + break :b try self.sema.pt.intern(.{ .int = .{ + .ty = enum_tag_info.tag_ty, + .storage = .{ .u64 = name_index }, } }); } else b: { // Fields are explicitly numbered break :b enum_tag_info.values.get(ip)[name_index]; }; + const tag = try self.sema.pt.intern(.{ .enum_tag = .{ + .ty = union_info.enum_tag_ty, + .int = tag_int, + } }); const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); const val = try self.parseExpr(field_node, field_type); return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index f741a7d206db..d9946b4985a7 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -53,26 +53,23 @@ test "union" { try expectEqual(union2.y, true); } - // Skip because explicit tags aren't yet working as expected - return error.SkipZigTest; - // Explicit tag - // { - // const Tag = enum(i128) { - // x = -1, - // y = 2, - // }; - // const Union = union(Tag) { - // x: f32, - // y: bool, - // }; - - // const union1: Union = @import("zon/union1.zon"); - // const union2: Union = @import("zon/union2.zon"); - - // try expectEqual(union1.x, 1.5); - // try expectEqual(union2.y, true); - // } + { + const Tag = enum(i128) { + x = -1, + y = 2, + }; + const Union = union(Tag) { + x: f32, + y: bool, + }; + + const union1: Union = @import("zon/union1.zon"); + const union2: Union = @import("zon/union2.zon"); + + try expectEqual(union1.x, 1.5); + try expectEqual(union2.y, true); + } } test "struct" { From 23b9011d4a59bd169771f4805c21c6c712a77409 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 12 Nov 2024 19:26:26 -0800 Subject: [PATCH 16/51] Parses void union fields from enum literals --- src/zon.zig | 46 ++++++++++++++++++++---------------- test/behavior/zon.zig | 16 +++++++++++++ test/behavior/zon/union3.zon | 1 + test/behavior/zon/union4.zon | 1 + 4 files changed, 44 insertions(+), 20 deletions(-) create mode 100644 test/behavior/zon/union3.zon create mode 100644 test/behavior/zon/union4.zon diff --git a/src/zon.zig b/src/zon.zig index 211400cb7af1..e7671dc83f0e 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -1014,39 +1014,38 @@ fn parseStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !Inter fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const ip = &self.sema.pt.zcu.intern_pool; + const main_tokens = self.file.tree.nodes.items(.main_token); try res_ty.resolveFully(self.sema.pt); const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?; const enum_tag_info = union_info.loadTagType(ip); - if (tags[node] == .enum_literal) @panic("unimplemented"); - - var buf: [2]Ast.Node.Index = undefined; - const field_nodes = try self.fields(res_ty, &buf, node); - if (field_nodes.len > 1) { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); - } - const field_node = field_nodes[0]; - var field_name = try self.ident(self.file.tree.firstToken(field_node) - 2); - defer field_name.deinit(self.sema.gpa); - const field_name_string = try ip.getOrPutString( - self.sema.pt.zcu.gpa, - self.sema.pt.tid, - field_name.bytes, - .no_embedded_nulls, - ); + const field_name, const maybe_field_node = if (tags[node] == .enum_literal) b: { + const field_name = try self.identAsNullTerminatedString(main_tokens[node]); + break :b .{ field_name, null }; + } else b: { + var buf: [2]Ast.Node.Index = undefined; + const field_nodes = try self.fields(res_ty, &buf, node); + if (field_nodes.len > 1) { + return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + } + const field_node = field_nodes[0]; + const field_name_token = self.file.tree.firstToken(field_node) - 2; + const field_name = try self.identAsNullTerminatedString(field_name_token); + break :b .{ field_name, field_node }; + }; - const name_index = enum_tag_info.nameIndex(ip, field_name_string) orelse { + const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); }; const tag_int = if (enum_tag_info.values.len == 0) b: { - // Fields are auto numbered + // Auto numbered fields break :b try self.sema.pt.intern(.{ .int = .{ .ty = enum_tag_info.tag_ty, .storage = .{ .u64 = name_index }, } }); } else b: { - // Fields are explicitly numbered + // Explicitly numbered fields break :b enum_tag_info.values.get(ip)[name_index]; }; const tag = try self.sema.pt.intern(.{ .enum_tag = .{ @@ -1054,7 +1053,14 @@ fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In .int = tag_int, } }); const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); - const val = try self.parseExpr(field_node, field_type); + const val = if (maybe_field_node) |field_node| b: { + break :b try self.parseExpr(field_node, field_type); + } else b: { + if (field_type.toIntern() != .void_type) { + return self.fail(.{ .node_abs = node }, "expected {}", .{field_type.fmt(self.sema.pt)}); + } + break :b .void_value; + }; return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ .ty = res_ty.toIntern(), .tag = tag, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index d9946b4985a7..4c99e374d69d 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -30,13 +30,18 @@ test "union" { 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"); + const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); + try expectEqual(union3.z, {}); + try expectEqual(union4.z, {}); } // Inferred tag @@ -44,13 +49,18 @@ test "union" { const Union = union(enum) { 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"); + const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); + try expectEqual(union3.z, {}); + try expectEqual(union4.z, {}); } // Explicit tag @@ -58,17 +68,23 @@ test "union" { 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"); + const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); + try expectEqual(union3.z, {}); + try expectEqual(union4.z, {}); } } 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/union4.zon b/test/behavior/zon/union4.zon new file mode 100644 index 000000000000..4224d9968bd1 --- /dev/null +++ b/test/behavior/zon/union4.zon @@ -0,0 +1 @@ +.{ .z = {} } From ece016acf428c48c6f64e1e78dab709fa9a30936 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 12 Nov 2024 20:39:31 -0800 Subject: [PATCH 17/51] Parses structs to known result types --- src/zon.zig | 63 ++++++++++- test/behavior/zon.zig | 162 +++++++++++++++-------------- test/behavior/zon/empty_struct.zon | 1 - 3 files changed, 145 insertions(+), 81 deletions(-) delete mode 100644 test/behavior/zon/empty_struct.zon diff --git a/src/zon.zig b/src/zon.zig index e7671dc83f0e..41e055b7e417 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -893,7 +893,7 @@ fn parseStructOrTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !Inter const ip = &self.sema.pt.zcu.intern_pool; return switch (ip.indexToKey(res_ty.toIntern())) { .tuple_type => self.parseTuple(node, res_ty), - .struct_type => @panic("unimplemented"), + .struct_type => self.parseStruct(node, res_ty), else => self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}), }; } @@ -930,13 +930,72 @@ fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } +fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; + + try res_ty.resolveFully(self.sema.pt); + const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; + + var buf: [2]Ast.Node.Index = undefined; + const field_nodes = try self.fields(res_ty, &buf, node); + + const field_values = try gpa.alloc(InternPool.Index, struct_info.field_names.len); + defer gpa.free(field_values); + + const field_defaults = struct_info.field_inits.get(ip); + for (0..field_values.len) |i| { + field_values[i] = if (i < field_defaults.len) field_defaults[i] else .none; + } + + for (field_nodes) |field_node| { + const field_name_token = self.file.tree.firstToken(field_node) - 2; + const field_name = try self.identAsNullTerminatedString(field_name_token); + + const name_index = struct_info.nameIndex(ip, field_name) orelse { + return self.fail( + .{ .node_abs = field_node }, + "unexpected field {}", + .{field_name.fmt(ip)}, + ); + }; + + const field_type = Type.fromInterned(struct_info.field_types.get(ip)[name_index]); + if (field_values[name_index] != .none) { + return self.fail( + .{ .node_abs = field_node }, + "duplicate field {}", + .{field_name.fmt(ip)}, + ); + } + field_values[name_index] = try self.parseExpr(field_node, field_type); + } + + const field_names = struct_info.field_names.get(ip); + for (field_values, field_names) |*value, name| { + if (value.* == .none) return self.fail( + .{ .node_abs = node }, + "missing field {}", + .{name.fmt(ip)}, + ); + } + + return self.sema.pt.intern(.{ .aggregate = .{ .ty = res_ty.toIntern(), .storage = .{ + .elems = field_values, + } } }); +} + fn parsePointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); if (ptr_info.flags.size != .Slice) { - return self.fail(.{ .node_abs = node }, "ZON import cannot be coerced to non slice pointer", .{}); + return self.fail( + .{ .node_abs = node }, + "ZON import cannot be coerced to non slice pointer", + .{}, + ); } const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1"; diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 4c99e374d69d..58fcca53a248 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -89,15 +89,23 @@ test "union" { } 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"))); + + // The skipped parts are failing because we need to resolve an issue where we intern whole number + // floats incorrectly (they get parsed as integers and then we try to store them that way, they + // should just be parsed as floats) return error.SkipZigTest; - // try expectEqual(.{}, @import("zon/vec0.zon")); - // try expectEqual(.{ .x = 1.5 }, @import("zon/vec1.zon")); - // try expectEqual(.{ .x = 1.5, .y = 2 }, @import("zon/vec2.zon")); - // try expectEqual(.{ .@"0" = 1.5, .foo = 2 }, @import("zon/escaped_struct.zon")); - // try expectEqual(.{}, @import("zon/empty_struct.zon")); } test "struct default fields" { + // We're skipping this for the same reason we skip some of the other struct tests return error.SkipZigTest; // const Vec3 = struct { // x: f32, @@ -110,11 +118,10 @@ test "struct default fields" { } test "struct enum field" { - return error.SkipZigTest; - // const Struct = struct { - // x: enum { x, y, z }, - // }; - // try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon"))); + const Struct = struct { + x: enum { x, y, z }, + }; + try expectEqual(Struct{ .x = .z }, @as(Struct, @import("zon/enum_field.zon"))); } test "tuple" { @@ -212,60 +219,60 @@ test "enum literals" { } test "int" { - return error.SkipZigTest; - // const expected = .{ - // // Test various numbers and types - // @as(u8, 10), - // @as(i16, 24), - // @as(i14, -4), - // @as(i32, -123), - - // // Test limits - // @as(i8, 127), - // @as(i8, -128), - - // // Test characters - // @as(u8, 'a'), - // @as(u8, 'z'), - - // // Test big integers - // @as(u65, 36893488147419103231), - // @as(u65, 36893488147419103231), - // @as(i128, -18446744073709551615), // Only a big int due to negation - // @as(i128, -9223372036854775809), // Only a big int due to negation - - // // Test big integer limits - // @as(i66, 36893488147419103231), - // @as(i66, -36893488147419103232), - - // // Test parsing whole number floats as integers - // @as(i8, -1), - // @as(i8, 123), - - // // Test non-decimal integers - // @as(i16, 0xff), - // @as(i16, -0xff), - // @as(i16, 0o77), - // @as(i16, -0o77), - // @as(i16, 0b11), - // @as(i16, -0b11), - - // // Test non-decimal big integers - // @as(u65, 0x1ffffffffffffffff), - // @as(i66, 0x1ffffffffffffffff), - // @as(i66, -0x1ffffffffffffffff), - // @as(u65, 0x1ffffffffffffffff), - // @as(i66, 0x1ffffffffffffffff), - // @as(i66, -0x1ffffffffffffffff), - // @as(u65, 0x1ffffffffffffffff), - // @as(i66, 0x1ffffffffffffffff), - // @as(i66, -0x1ffffffffffffffff), - // }; - // const actual: @TypeOf(expected) = @import("zon/ints.zon"); - // try expectEqual(expected, actual); + const expected = .{ + // Test various numbers and types + @as(u8, 10), + @as(i16, 24), + @as(i14, -4), + @as(i32, -123), + + // Test limits + @as(i8, 127), + @as(i8, -128), + + // Test characters + @as(u8, 'a'), + @as(u8, 'z'), + + // Test big integers + @as(u65, 36893488147419103231), + @as(u65, 36893488147419103231), + @as(i128, -18446744073709551615), // Only a big int due to negation + @as(i128, -9223372036854775809), // Only a big int due to negation + + // Test big integer limits + @as(i66, 36893488147419103231), + @as(i66, -36893488147419103232), + + // Test parsing whole number floats as integers + @as(i8, -1), + @as(i8, 123), + + // Test non-decimal integers + @as(i16, 0xff), + @as(i16, -0xff), + @as(i16, 0o77), + @as(i16, -0o77), + @as(i16, 0b11), + @as(i16, -0b11), + + // Test non-decimal big integers + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + @as(u65, 0x1ffffffffffffffff), + @as(i66, 0x1ffffffffffffffff), + @as(i66, -0x1ffffffffffffffff), + }; + const actual: @TypeOf(expected) = @import("zon/ints.zon"); + try expectEqual(expected, actual); } test "floats" { + // See issue on disabled struct tests return error.SkipZigTest; // const expected = .{ // // Test decimals @@ -304,22 +311,21 @@ test "floats" { } test "inf and nan" { - return error.SkipZigTest; - // // comptime float - // { - // const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); - // try expect(std.math.isNan(actual[0])); - // try expect(std.math.isNan(actual[1])); - // try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); - // try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); - // } + // comptime float + { + const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isNan(actual[1])); + try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); + try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); + } - // // f32 - // { - // const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); - // try expect(std.math.isNan(actual[0])); - // try expect(std.math.isNan(actual[1])); - // try expect(std.math.isPositiveInf(actual[2])); - // try expect(std.math.isNegativeInf(actual[3])); - // } + // f32 + { + const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + try expect(std.math.isNan(actual[0])); + try expect(std.math.isNan(actual[1])); + try expect(std.math.isPositiveInf(actual[2])); + try expect(std.math.isNegativeInf(actual[3])); + } } diff --git a/test/behavior/zon/empty_struct.zon b/test/behavior/zon/empty_struct.zon deleted file mode 100644 index 47c47bc057a0..000000000000 --- a/test/behavior/zon/empty_struct.zon +++ /dev/null @@ -1 +0,0 @@ -.{} From 0cbfb12723ff383395d00419f8aeecc7f8bf64e0 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 13 Nov 2024 14:50:57 -0800 Subject: [PATCH 18/51] Fixes parsing integers as floats --- src/zon.zig | 209 ++++++++++++++++++++++++++++-------------- test/behavior/zon.zig | 101 ++++++++++---------- 2 files changed, 184 insertions(+), 126 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 41e055b7e417..10ca12a1f2a8 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -252,7 +252,8 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { .void => return self.parseVoid(node), .bool => return self.parseBool(node), - .int, .comptime_int, .float, .comptime_float => return self.parseNumber(node, res_ty), + .int, .comptime_int => return self.parseInt(node, res_ty), + .float, .comptime_float => return self.parseFloat(node, res_ty), .optional => return self.parseOptional(node, res_ty), .null => return self.parseNull(node), .@"enum" => return self.parseEnum(node, res_ty), @@ -544,7 +545,7 @@ fn parseBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return self.fail(.{ .node_abs = node }, "expected bool", .{}); } -fn parseNumber( +fn parseInt( self: LowerZon, node: Ast.Node.Index, res_ty: Type, @@ -693,64 +694,126 @@ fn parseNumber( unreachable; }; const float = if (is_negative == null) unsigned_float else -unsigned_float; - switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { - .float, .comptime_float => return self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { - 16 => .{ .f16 = @floatCast(float) }, - 32 => .{ .f32 = @floatCast(float) }, - 64 => .{ .f64 = @floatCast(float) }, - 80 => .{ .f80 = @floatCast(float) }, - 128 => .{ .f128 = float }, - else => unreachable, - }, - } }), - .int, .comptime_int => { - // Check for fractional components - if (@rem(float, 1) != 0) { - return self.fail( - .{ .node_abs = num_lit_node }, - "fractional component prevents float value '{}' from coercion to type '{}'", - .{ float, res_ty.fmt(self.sema.pt) }, - ); - } - // Create a rational representation of the float - var rational = try std.math.big.Rational.init(gpa); - defer rational.deinit(); - rational.setFloat(f128, float) catch |err| switch (err) { - error.NonFiniteFloat => unreachable, - error.OutOfMemory => return error.OutOfMemory, - }; + // Check for fractional components + if (@rem(float, 1) != 0) { + return self.fail( + .{ .node_abs = num_lit_node }, + "fractional component prevents float value '{}' from coercion to type '{}'", + .{ float, res_ty.fmt(self.sema.pt) }, + ); + } - // 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)); - if (is_negative != null) rational.negate(); + // Create a rational representation of the float + var rational = try std.math.big.Rational.init(gpa); + defer rational.deinit(); + rational.setFloat(f128, float) catch |err| switch (err) { + error.NonFiniteFloat => unreachable, + error.OutOfMemory => return error.OutOfMemory, + }; - // Check that the result is in range of the result type - const int_info = res_ty.intInfo(self.sema.pt.zcu); - if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { - return self.fail( - .{ .node_abs = num_lit_node }, - "float value '{}' cannot be stored in integer type '{}'", - .{ float, res_ty.fmt(self.sema.pt) }, - ); - } + // 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)); + if (is_negative != null) rational.negate(); + + // Check that the result is in range of the result type + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + .{ .node_abs = num_lit_node }, + "float value '{}' cannot be stored in integer type '{}'", + .{ float, res_ty.fmt(self.sema.pt) }, + ); + } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ - .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .big_int = rational.p.toConst() }, - }, - }); + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = rational.p.toConst() }, }, - else => unreachable, - } + }); }, .failure => |err| return self.failWithNumberError(token, err), } }, + .identifier => { + unreachable; // Decide what error to give here + }, + else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), + } +} + +fn parseFloat( + self: LowerZon, + node: Ast.Node.Index, + res_ty: Type, +) !InternPool.Index { + @setFloatMode(.strict); + + const tags = self.file.tree.nodes.items(.tag); + const main_tokens = self.file.tree.nodes.items(.main_token); + const num_lit_node, const is_negative = if (tags[node] == .negation) b: { + const data = self.file.tree.nodes.items(.data); + break :b .{ + data[node].lhs, + node, + }; + } else .{ + node, + null, + }; + switch (tags[num_lit_node]) { + .char_literal => { + const token = main_tokens[num_lit_node]; + const token_bytes = self.file.tree.tokenSlice(token); + var char: i64 = switch (std.zig.string_literal.parseCharLiteral(token_bytes)) { + .success => |char| char, + .failure => |err| { + const offset = self.file.tree.tokens.items(.start)[token]; + return self.fail( + .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, + "{}", + .{err.fmtWithSource(token_bytes)}, + ); + }, + }; + if (is_negative != null) char = -char; + return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatFromInt(char) }, + .f32_type => .{ .f32 = @floatFromInt(char) }, + .f64_type => .{ .f64 = @floatFromInt(char) }, + .f80_type => .{ .f80 = @floatFromInt(char) }, + .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(char) }, + else => unreachable, + }, + } }); + }, + .number_literal => { + const token = main_tokens[num_lit_node]; + const token_bytes = self.file.tree.tokenSlice(token); + + var float = std.fmt.parseFloat(f128, token_bytes) catch |err| switch (err) { + error.InvalidCharacter => return self.fail(.{ .node_abs = num_lit_node }, "invalid character", .{}), + }; + if (is_negative != null) float = -float; + + return self.sema.pt.intern(.{ + .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatCast(float) }, + .f32_type => .{ .f32 = @floatCast(float) }, + .f64_type => .{ .f64 = @floatCast(float) }, + .f80_type => .{ .f80 = @floatCast(float) }, + .f128_type, .comptime_float_type => .{ .f128 = float }, + else => unreachable, + }, + }, + }); + }, .identifier => { switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { .float, .comptime_float => {}, @@ -765,28 +828,32 @@ fn parseNumber( }); if (values.get(bytes)) |value| { return switch (value) { - .nan => self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { - 16 => .{ .f16 = std.math.nan(f16) }, - 32 => .{ .f32 = std.math.nan(f32) }, - 64 => .{ .f64 = std.math.nan(f64) }, - 80 => .{ .f80 = std.math.nan(f80) }, - 128 => .{ .f128 = std.math.nan(f128) }, - else => unreachable, + .nan => self.sema.pt.intern(.{ + .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = std.math.nan(f16) }, + .f32_type => .{ .f32 = std.math.nan(f32) }, + .f64_type => .{ .f64 = std.math.nan(f64) }, + .f80_type => .{ .f80 = std.math.nan(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = std.math.nan(f128) }, + else => unreachable, + }, }, - } }), - .inf => self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.floatBits(self.sema.pt.zcu.getTarget())) { - 16 => .{ .f16 = if (is_negative == null) std.math.inf(f16) else -std.math.inf(f16) }, - 32 => .{ .f32 = if (is_negative == null) std.math.inf(f32) else -std.math.inf(f32) }, - 64 => .{ .f64 = if (is_negative == null) std.math.inf(f64) else -std.math.inf(f64) }, - 80 => .{ .f80 = if (is_negative == null) std.math.inf(f80) else -std.math.inf(f80) }, - 128 => .{ .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128) }, - else => unreachable, + }), + .inf => self.sema.pt.intern(.{ + .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = if (is_negative == null) std.math.inf(f16) else -std.math.inf(f16) }, + .f32_type => .{ .f32 = if (is_negative == null) std.math.inf(f32) else -std.math.inf(f32) }, + .f64_type => .{ .f64 = if (is_negative == null) std.math.inf(f64) else -std.math.inf(f64) }, + .f80_type => .{ .f80 = if (is_negative == null) std.math.inf(f80) else -std.math.inf(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128) }, + else => unreachable, + }, }, - } }), + }), }; } return self.fail(.{ .node_abs = num_lit_node }, "use of unknown identifier '{s}'", .{bytes}); diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 58fcca53a248..6394a648207c 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -91,30 +91,23 @@ test "union" { test "struct" { const Vec0 = struct {}; const Vec1 = struct { x: f32 }; - // const Vec2 = struct { x: f32, y: f32 }; - // const Escaped = struct { @"0": f32, foo: 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"))); - - // The skipped parts are failing because we need to resolve an issue where we intern whole number - // floats incorrectly (they get parsed as integers and then we try to store them that way, they - // should just be parsed as floats) - return error.SkipZigTest; + 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" { - // We're skipping this for the same reason we skip some of the other struct tests - return error.SkipZigTest; - // 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 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); } test "struct enum field" { @@ -272,42 +265,40 @@ test "int" { } test "floats" { - // See issue on disabled struct tests - return error.SkipZigTest; - // const expected = .{ - // // Test decimals - // @as(f16, 0.5), - // @as(f32, 123.456), - // @as(f64, -123.456), - // @as(f128, 42.5), - - // // Test whole numbers with and without decimals - // @as(f16, 5.0), - // @as(f16, 5.0), - // @as(f32, -102), - // @as(f32, -102), - - // // Test characters and negated characters - // @as(f32, 'a'), - // @as(f32, 'z'), - // @as(f32, -'z'), - - // // Test big integers - // @as(f32, 36893488147419103231), - // @as(f32, -36893488147419103231), - // @as(f128, 0x1ffffffffffffffff), - // @as(f32, 0x1ffffffffffffffff), - - // // Exponents, underscores - // @as(f32, 123.0E+77), - - // // Hexadecimal - // @as(f32, 0x103.70p-5), - // @as(f32, -0x103.70), - // @as(f32, 0x1234_5678.9ABC_CDEFp-10), - // }; - // const actual: @TypeOf(expected) = @import("zon/floats.zon"); - // try expectEqual(actual, expected); + const expected = .{ + // Test decimals + @as(f16, 0.5), + @as(f32, 123.456), + @as(f64, -123.456), + @as(f128, 42.5), + + // Test whole numbers with and without decimals + @as(f16, 5.0), + @as(f16, 5.0), + @as(f32, -102), + @as(f32, -102), + + // Test characters and negated characters + @as(f32, 'a'), + @as(f32, 'z'), + @as(f32, -'z'), + + // Test big integers + @as(f32, 36893488147419103231), + @as(f32, -36893488147419103231), + @as(f128, 0x1ffffffffffffffff), + @as(f32, 0x1ffffffffffffffff), + + // Exponents, underscores + @as(f32, 123.0E+77), + + // Hexadecimal + @as(f32, 0x103.70p-5), + @as(f32, -0x103.70), + @as(f32, 0x1234_5678.9ABC_CDEFp-10), + }; + const actual: @TypeOf(expected) = @import("zon/floats.zon"); + try expectEqual(actual, expected); } test "inf and nan" { From a994c1449416bb7e84610f1ad8b0f8d9b1b06f86 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 26 Nov 2024 14:41:24 -0800 Subject: [PATCH 19/51] Removes old implementation, renames parse to lower --- src/zon.zig | 314 +++++++--------------------------------------------- 1 file changed, 38 insertions(+), 276 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 10ca12a1f2a8..62352da2bee5 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -41,7 +41,7 @@ pub fn lower( const data = tree.nodes.items(.data); const root = data[0].lhs; - return lower_zon.parseExpr(root, res_ty); + return lower_zon.lowerExpr(root, res_ty); } fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { @@ -242,26 +242,20 @@ const FieldTypes = union(enum) { } }; -fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!InternPool.Index { - const gpa = self.sema.gpa; - const ip = &self.sema.pt.zcu.intern_pool; - const data = self.file.tree.nodes.items(.data); - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - +fn lowerExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!InternPool.Index { switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { - .void => return self.parseVoid(node), - .bool => return self.parseBool(node), - .int, .comptime_int => return self.parseInt(node, res_ty), - .float, .comptime_float => return self.parseFloat(node, res_ty), - .optional => return self.parseOptional(node, res_ty), - .null => return self.parseNull(node), - .@"enum" => return self.parseEnum(node, res_ty), - .enum_literal => return self.parseEnumLiteral(node, res_ty), - .array => return self.parseArray(node, res_ty), - .@"struct" => return self.parseStructOrTuple(node, res_ty), - .@"union" => return self.parseUnion(node, res_ty), - .pointer => return self.parsePointer(node, res_ty), + .void => return self.lowerVoid(node), + .bool => return self.lowerBool(node), + .int, .comptime_int => return self.lowerInt(node, res_ty), + .float, .comptime_float => return self.lowerFloat(node, res_ty), + .optional => return self.lowerOptional(node, res_ty), + .null => return self.lowerNull(node), + .@"enum" => return self.lowerEnum(node, res_ty), + .enum_literal => return self.lowerEnumLiteral(node, res_ty), + .array => return self.lowerArray(node, res_ty), + .@"struct" => return self.lowerStructOrTuple(node, res_ty), + .@"union" => return self.lowerUnion(node, res_ty), + .pointer => return self.lowerPointer(node, res_ty), .type, .noreturn, @@ -273,243 +267,11 @@ fn parseExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .frame, .@"anyframe", .vector, - => { - @panic("unimplemented"); - }, - } - - // If the result type is slice, and our AST Node is not a slice, recurse and then take the - // address of the result so attempt to coerce it into a slice. - const result_is_slice = res_ty.isSlice(self.sema.pt.zcu); - const ast_is_pointer = switch (tags[node]) { - .string_literal, .multiline_string_literal => true, - else => false, - }; - if (result_is_slice and !ast_is_pointer) { - const val = try self.parseExpr(node, res_ty.childType(self.sema.pt.zcu)); - const val_type = ip.typeOf(val); - const ptr_type = try self.sema.pt.ptrTypeSema(.{ - .child = val_type, - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - }); - _ = ptr_type; - @panic("unimplemented"); - // return ip.get(gpa, self.sema.pt.tid, .{ .ptr = .{ - // .ty = ptr_type.toIntern(), - // .base_addr = .{ .anon_decl = .{ - // .orig_ty = ptr_type.toIntern(), - // .val = val, - // } }, - // .byte_offset = 0, - // } }); - } - - switch (tags[node]) { - .identifier => { - const token = main_tokens[node]; - var litIdent = try self.ident(token); - defer litIdent.deinit(gpa); - - const LitIdent = enum { nan, inf }; - const values = std.StaticStringMap(LitIdent).initComptime(.{ - .{ "nan", .nan }, - .{ "inf", .inf }, - }); - if (values.get(litIdent.bytes)) |value| { - return switch (value) { - .nan => self.sema.pt.intern(.{ .float = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), - .storage = .{ .f128 = std.math.nan(f128) }, - } }), - .inf => try self.sema.pt.intern(.{ .float = .{ - .ty = try self.sema.pt.intern(.{ .simple_type = .comptime_float }), - .storage = .{ .f128 = std.math.inf(f128) }, - } }), - }; - } - return self.fail(.{ .node_abs = node }, "use of unknown identifier '{s}'", .{litIdent.bytes}); - }, - .string_literal => { - const token = main_tokens[node]; - const raw_string = self.file.tree.tokenSlice(token); - - var bytes = std.ArrayListUnmanaged(u8){}; - defer bytes.deinit(gpa); - - switch (try std.zig.string_literal.parseWrite(bytes.writer(gpa), raw_string)) { - .success => {}, - .failure => |err| { - const offset = self.file.tree.tokens.items(.start)[token]; - return self.fail( - .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, - "{}", - .{err.fmtWithSource(raw_string)}, - ); - }, - } - const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); - const array_ty = try self.sema.pt.intern(.{ .array_type = .{ - .len = bytes.items.len, - .sentinel = .zero_u8, - .child = .u8_type, - } }); - const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = string }, - } }); - return self.sema.pt.intern(.{ .slice = .{ - .ty = .slice_const_u8_sentinel_0_type, - .ptr = try self.sema.pt.intern(.{ .ptr = .{ - .ty = .manyptr_const_u8_sentinel_0_type, - .base_addr = .{ .uav = .{ - .orig_ty = .slice_const_u8_sentinel_0_type, - .val = array_val, - } }, - .byte_offset = 0, - } }), - .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), - } }); - }, - .multiline_string_literal => { - var bytes = std.ArrayListUnmanaged(u8){}; - defer bytes.deinit(gpa); - - var parser = std.zig.string_literal.multilineParser(bytes.writer(gpa)); - var tok_i = data[node].lhs; - while (tok_i <= data[node].rhs) : (tok_i += 1) { - try parser.line(self.file.tree.tokenSlice(tok_i)); - } - - const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); - const array_ty = try self.sema.pt.intern(.{ .array_type = .{ - .len = bytes.items.len, - .sentinel = .zero_u8, - .child = .u8_type, - } }); - const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = string }, - } }); - return self.sema.pt.intern(.{ .slice = .{ - .ty = .slice_const_u8_sentinel_0_type, - .ptr = try self.sema.pt.intern(.{ .ptr = .{ - .ty = .manyptr_const_u8_sentinel_0_type, - .base_addr = .{ .uav = .{ - .orig_ty = .slice_const_u8_sentinel_0_type, - .val = array_val, - } }, - .byte_offset = 0, - } }), - .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), - } }); - }, - .struct_init_one, - .struct_init_one_comma, - .struct_init_dot_two, - .struct_init_dot_two_comma, - .struct_init_dot, - .struct_init_dot_comma, - .struct_init, - .struct_init_comma, - => { - var buf: [2]Ast.Node.Index = undefined; - const struct_init = self.file.tree.fullStructInit(&buf, node).?; - if (struct_init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = struct_init.ast.type_expr }, "type expressions not allowed in ZON", .{}); - } - const types = try gpa.alloc(InternPool.Index, struct_init.ast.fields.len); - defer gpa.free(types); - - const values = try gpa.alloc(InternPool.Index, struct_init.ast.fields.len); - defer gpa.free(values); - - var names = std.AutoArrayHashMapUnmanaged(NullTerminatedString, void){}; - defer names.deinit(gpa); - try names.ensureTotalCapacity(gpa, struct_init.ast.fields.len); - - const rt_field_types = try FieldTypes.init(res_ty, self.sema); - for (struct_init.ast.fields, 0..) |field, i| { - const name_token = self.file.tree.firstToken(field) - 2; - const name = try self.identAsNullTerminatedString(name_token); - const gop = names.getOrPutAssumeCapacity(name); - if (gop.found_existing) { - return self.fail(.{ .token_abs = name_token }, "duplicate field", .{}); - } - - const elem_ty = rt_field_types.get(name, self.sema.pt.zcu) orelse @panic("unimplemented"); - - values[i] = try self.parseExpr(field, elem_ty); - types[i] = ip.typeOf(values[i]); - } - - @panic("unimplemented"); - // const struct_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ - // .types = types, - // .names = names.entries.items(.key), - // .values = values, - // }); - // return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ - // .ty = struct_type, - // .storage = .{ .elems = values }, - // } }); - }, - .array_init_one, - .array_init_one_comma, - .array_init_dot_two, - .array_init_dot_two_comma, - .array_init_dot, - .array_init_dot_comma, - .array_init, - .array_init_comma, - => { - var buf: [2]Ast.Node.Index = undefined; - const array_init = self.file.tree.fullArrayInit(&buf, node).?; - if (array_init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = array_init.ast.type_expr }, "type expressions not allowed in ZON", .{}); - } - const types = try gpa.alloc(InternPool.Index, array_init.ast.elements.len); - defer gpa.free(types); - const values = try gpa.alloc(InternPool.Index, array_init.ast.elements.len); - defer gpa.free(values); - for (array_init.ast.elements, 0..) |elem, i| { - const elem_ty = b: { - const type_tag = res_ty.zigTypeTagOrPoison(self.sema.pt.zcu) catch break :b null; - switch (type_tag) { - .array => break :b res_ty.childType(self.sema.pt.zcu), - .@"struct" => { - try res_ty.resolveFully(self.sema.pt); - if (i >= res_ty.structFieldCount(self.sema.pt.zcu)) break :b null; - break :b res_ty.fieldType(i, self.sema.pt.zcu); - }, - else => break :b null, - } - }; - values[i] = try self.parseExpr(elem, elem_ty orelse @panic("unimplemented")); - types[i] = ip.typeOf(values[i]); - } - - @panic("unimplemented"); - // const tuple_type = try ip.getAnonStructType(gpa, self.sema.pt.tid, .{ - // .types = types, - // .names = &.{}, - // .values = values, - // }); - // return ip.get(gpa, self.sema.pt.tid, .{ .aggregate = .{ - // .ty = tuple_type, - // .storage = .{ .elems = values }, - // } }); - }, - else => {}, + => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), } - - return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } -fn parseVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { +fn lowerVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const data = self.file.tree.nodes.items(.data); @@ -520,7 +282,7 @@ fn parseVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return self.fail(.{ .node_abs = node }, "expected void", .{}); } -fn parseBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { +fn lowerBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { const gpa = self.sema.gpa; const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); @@ -545,7 +307,7 @@ fn parseBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return self.fail(.{ .node_abs = node }, "expected bool", .{}); } -fn parseInt( +fn lowerInt( self: LowerZon, node: Ast.Node.Index, res_ty: Type, @@ -744,7 +506,7 @@ fn parseInt( } } -fn parseFloat( +fn lowerFloat( self: LowerZon, node: Ast.Node.Index, res_ty: Type, @@ -862,7 +624,7 @@ fn parseFloat( } } -fn parseOptional(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerOptional(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); @@ -874,11 +636,11 @@ fn parseOptional(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool return self.sema.pt.intern(.{ .opt = .{ .ty = res_ty.toIntern(), - .val = try self.parseExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), + .val = try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), } }); } -fn parseNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { +fn lowerNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const main_tokens = self.file.tree.nodes.items(.main_token); @@ -891,7 +653,7 @@ fn parseNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } -fn parseArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const gpa = self.sema.gpa; const array_info = res_ty.arrayInfo(self.sema.pt.zcu); @@ -906,7 +668,7 @@ fn parseArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In defer gpa.free(elems); for (elem_nodes, 0..) |elem_node, i| { - elems[i] = try self.parseExpr(elem_node, array_info.elem_type); + elems[i] = try self.lowerExpr(elem_node, array_info.elem_type); } if (array_info.sentinel) |sentinel| { @@ -919,7 +681,7 @@ fn parseArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } -fn parseEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const main_tokens = self.file.tree.nodes.items(.main_token); const tags = self.file.tree.nodes.items(.tag); const ip = &self.sema.pt.zcu.intern_pool; @@ -941,7 +703,7 @@ fn parseEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Ind return value.toIntern(); } -fn parseEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const main_tokens = self.file.tree.nodes.items(.main_token); const tags = self.file.tree.nodes.items(.tag); const ip = &self.sema.pt.zcu.intern_pool; @@ -956,16 +718,16 @@ fn parseEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternP }); } -fn parseStructOrTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerStructOrTuple(self: LowerZon, node: Ast.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.parseTuple(node, res_ty), - .struct_type => self.parseStruct(node, res_ty), + .tuple_type => self.lowerTuple(node, res_ty), + .struct_type => self.lowerStruct(node, res_ty), else => self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}), }; } -fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; @@ -988,7 +750,7 @@ fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In defer gpa.free(elems); for (elems, elem_nodes, field_types) |*elem, elem_node, field_type| { - elem.* = try self.parseExpr(elem_node, Type.fromInterned(field_type)); + elem.* = try self.lowerExpr(elem_node, Type.fromInterned(field_type)); } return self.sema.pt.intern(.{ .aggregate = .{ @@ -997,7 +759,7 @@ fn parseTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } -fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; @@ -1035,7 +797,7 @@ fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I .{field_name.fmt(ip)}, ); } - field_values[name_index] = try self.parseExpr(field_node, field_type); + field_values[name_index] = try self.lowerExpr(field_node, field_type); } const field_names = struct_info.field_names.get(ip); @@ -1052,7 +814,7 @@ fn parseStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I } } }); } -fn parsePointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); @@ -1069,7 +831,7 @@ fn parsePointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. 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) { if (tags[node] == .string_literal or tags[node] == .multiline_string_literal) { - return self.parseStringLiteral(node, res_ty); + return self.lowerStringLiteral(node, res_ty); } } @@ -1079,7 +841,7 @@ fn parsePointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. @panic("unimplemented"); } -fn parseStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const gpa = self.sema.gpa; const ip = &self.sema.pt.zcu.intern_pool; const main_tokens = self.file.tree.nodes.items(.main_token); @@ -1137,7 +899,7 @@ fn parseStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !Inter } }); } -fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); const ip = &self.sema.pt.zcu.intern_pool; const main_tokens = self.file.tree.nodes.items(.main_token); @@ -1180,7 +942,7 @@ fn parseUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); const val = if (maybe_field_node) |field_node| b: { - break :b try self.parseExpr(field_node, field_type); + break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { return self.fail(.{ .node_abs = node }, "expected {}", .{field_type.fmt(self.sema.pt)}); From fa4a7d817c648d128bc258f5213802a364d24133 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 26 Nov 2024 16:15:45 -0800 Subject: [PATCH 20/51] Implements slice coercion Also fixes compile error on 32 bit systems. All zon non compile error behavior tests now pass locally. --- src/zon.zig | 64 +++++++++++++++++++++++++++++-- test/behavior/zon.zig | 89 +++++++++++++++++++++---------------------- 2 files changed, 104 insertions(+), 49 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 62352da2bee5..6236fe185c29 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -664,7 +664,7 @@ fn lowerArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); } - const elems = try gpa.alloc(InternPool.Index, array_info.len + @intFromBool(array_info.sentinel != null)); + const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(array_info.sentinel != null)); defer gpa.free(elems); for (elem_nodes, 0..) |elem_node, i| { @@ -816,6 +816,8 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { const tags = self.file.tree.nodes.items(.tag); + const ip = &self.sema.pt.zcu.intern_pool; + const gpa = self.sema.gpa; const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); @@ -827,6 +829,7 @@ fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. ); } + // 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) { @@ -835,10 +838,63 @@ fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. } } - var buf: [2]Ast.Node.Index = undefined; + // Slice literals + var buf: [2]NodeIndex = undefined; const elem_nodes = try self.elements(res_ty, &buf, node); - _ = elem_nodes; - @panic("unimplemented"); + + const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); + defer gpa.free(elems); + + for (elem_nodes, 0..) |elem_node, i| { + elems[i] = try self.lowerExpr(elem_node, Type.fromInterned(ptr_info.child)); + } + + if (ptr_info.sentinel != .none) { + elems[elems.len - 1] = ptr_info.sentinel; + } + + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ + .len = elems.len, + .sentinel = ptr_info.sentinel, + .child = ptr_info.child, + } }); + + const array = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty, + .storage = .{ .elems = elems }, + } }); + + const many_item_ptr_type = try ip.get(gpa, self.sema.pt.tid, .{ .ptr_type = .{ + .child = ptr_info.child, + .sentinel = ptr_info.sentinel, + .flags = b: { + var flags = ptr_info.flags; + flags.size = .Many; + break :b flags; + }, + .packed_offset = ptr_info.packed_offset, + } }); + + const many_item_ptr = try ip.get(gpa, self.sema.pt.tid, .{ + .ptr = .{ + .ty = many_item_ptr_type, + .base_addr = .{ + .uav = .{ + .orig_ty = res_ty.toIntern(), + .val = array, + }, + }, + .byte_offset = 0, + }, + }); + + const len = (try self.sema.pt.intValue(Type.usize, elems.len)).toIntern(); + + return ip.get(gpa, self.sema.pt.tid, .{ .slice = .{ + .ty = res_ty.toIntern(), + .ptr = many_item_ptr, + .len = len, + } }); } fn lowerStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 6394a648207c..f0408fb18cfb 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -138,51 +138,50 @@ test "arrays" { } test "slices, arrays, tuples" { - return error.SkipZigTest; - // { - // 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/slice-1.zon"); - // try expectEqualSlices(u8, expected_slice, found_slice); - - // const expected_array: [1]u8 = .{1}; - // const found_array: [1]u8 = @import("zon/slice-1.zon"); - // try expectEqual(expected_array, found_array); - - // const T = struct { u8 }; - // const expected_tuple: T = .{1}; - // const found_tuple: T = @import("zon/slice-1.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); - // } + { + 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/slice-1.zon"); + try expectEqualSlices(u8, expected_slice, found_slice); + + const expected_array: [1]u8 = .{1}; + const found_array: [1]u8 = @import("zon/slice-1.zon"); + try expectEqual(expected_array, found_array); + + const T = struct { u8 }; + const expected_tuple: T = .{1}; + const found_tuple: T = @import("zon/slice-1.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" { From b14c22444a4115f0e030a3ecad61f10d49698f49 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 27 Nov 2024 15:16:39 -0800 Subject: [PATCH 21/51] Starts work on compile error tests --- src/Sema.zig | 1 + src/zon.zig | 6 +++++- test/cases/compile_errors/@import_zon_addr_slice.zig | 2 +- test/cases/compile_errors/@import_zon_array_len.zig | 5 ++--- .../compile_errors/@import_zon_double_negation_float.zig | 1 + .../cases/compile_errors/@import_zon_enum_embedded_null.zig | 4 +++- test/cases/compile_errors/@import_zon_invalid_character.zig | 3 ++- test/cases/compile_errors/@import_zon_invalid_number.zig | 2 +- test/cases/compile_errors/@import_zon_invalid_string.zig | 3 ++- .../compile_errors/@import_zon_leading_zero_in_integer.zig | 2 +- .../cases/compile_errors/@import_zon_number_fail_limits.zig | 3 ++- test/cases/compile_errors/@import_zon_struct_dup_field.zig | 3 ++- test/cases/compile_errors/@import_zon_unknown_ident.zig | 5 +++-- 13 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 1cf7d8b1b5a4..f8ec07c7efd0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14466,6 +14466,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. result.file, result.file_index, res_ty, + operand_src, ); return Air.internedToRef(interned); }, diff --git a/src/zon.zig b/src/zon.zig index 6236fe185c29..e89e0b492e14 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -21,6 +21,7 @@ const LowerZon = @This(); sema: *Sema, file: *File, file_index: Zcu.File.Index, +import_loc: LazySrcLoc, /// Lowers the given file as ZON. pub fn lower( @@ -28,11 +29,13 @@ pub fn lower( file: *File, file_index: Zcu.File.Index, res_ty: Type, + import_loc: LazySrcLoc, ) CompileError!InternPool.Index { const lower_zon: LowerZon = .{ .sema = sema, .file = file, .file_index = file_index, + .import_loc = import_loc, }; const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated if (tree.errors.len != 0) { @@ -64,6 +67,7 @@ fn fail( @branchHint(.cold); const src_loc = try self.lazySrcLoc(loc); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); + try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); return error.AnalysisFail; } @@ -502,7 +506,7 @@ fn lowerInt( .identifier => { unreachable; // Decide what error to give here }, - else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), + else => return self.fail(.{ .node_abs = num_lit_node }, "expected integer", .{}), } } diff --git a/test/cases/compile_errors/@import_zon_addr_slice.zig b/test/cases/compile_errors/@import_zon_addr_slice.zig index 0c9f803b977a..356a6878bb12 100644 --- a/test/cases/compile_errors/@import_zon_addr_slice.zig +++ b/test/cases/compile_errors/@import_zon_addr_slice.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/addr_slice.zon"); + const f: struct { value: []const i32 } = @import("zon/addr_slice.zon"); _ = f; } diff --git a/test/cases/compile_errors/@import_zon_array_len.zig b/test/cases/compile_errors/@import_zon_array_len.zig index c1fe43c612ba..6ae6b381f43f 100644 --- a/test/cases/compile_errors/@import_zon_array_len.zig +++ b/test/cases/compile_errors/@import_zon_array_len.zig @@ -8,6 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/array.zon // -// 2:22: error: expected type '[4]u8', found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}' -// note: destination has length 4 -// note: source has length 3 +// array.zon:1:2: error: expected [4]u8 +// tmp.zig:2:30: note: imported here diff --git a/test/cases/compile_errors/@import_zon_double_negation_float.zig b/test/cases/compile_errors/@import_zon_double_negation_float.zig index 3c3c05e9017e..def6c8613546 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_float.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_float.zig @@ -9,3 +9,4 @@ pub fn main() void { // imports=zon/double_negation_float.zon // // double_negation_float.zon:1:2: error: invalid ZON value +// tmp.zig:2:28: note: imported here diff --git a/test/cases/compile_errors/@import_zon_enum_embedded_null.zig b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig index fa9e31a878ff..c7d1dccf5c29 100644 --- a/test/cases/compile_errors/@import_zon_enum_embedded_null.zig +++ b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig @@ -1,6 +1,7 @@ const std = @import("std"); pub fn main() void { - const f = @import("zon/enum_embedded_null.zon"); + const E = enum { foo }; + const f: struct { E, E } = @import("zon/enum_embedded_null.zon"); _ = f; } @@ -10,3 +11,4 @@ pub fn main() void { // imports=zon/enum_embedded_null.zon // // enum_embedded_null.zon:2:6: error: identifier cannot contain null bytes +// tmp.zig:4:40: 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 index 3bf677bd8f98..a2bf474f63ea 100644 --- a/test/cases/compile_errors/@import_zon_invalid_character.zig +++ b/test/cases/compile_errors/@import_zon_invalid_character.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f = @import("zon/invalid_character.zon"); + const f: u8 = @import("zon/invalid_character.zon"); _ = f; } @@ -9,3 +9,4 @@ pub fn main() void { // imports=zon/invalid_character.zon // // invalid_character.zon:1:3: error: invalid escape character: 'a' +// tmp.zig:2:27: note: imported here diff --git a/test/cases/compile_errors/@import_zon_invalid_number.zig b/test/cases/compile_errors/@import_zon_invalid_number.zig index dba42b6b1511..a18f1631de2f 100644 --- a/test/cases/compile_errors/@import_zon_invalid_number.zig +++ b/test/cases/compile_errors/@import_zon_invalid_number.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f = @import("zon/invalid_number.zon"); + const f: u128 = @import("zon/invalid_number.zon"); _ = f; } diff --git a/test/cases/compile_errors/@import_zon_invalid_string.zig b/test/cases/compile_errors/@import_zon_invalid_string.zig index 7a6d686a385f..e103a4507447 100644 --- a/test/cases/compile_errors/@import_zon_invalid_string.zig +++ b/test/cases/compile_errors/@import_zon_invalid_string.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f = @import("zon/invalid_string.zon"); + const f: []const u8 = @import("zon/invalid_string.zon"); _ = f; } @@ -9,3 +9,4 @@ pub fn main() void { // imports=zon/invalid_string.zon // // invalid_string.zon:1:5: error: invalid escape character: 'a' +// tmp.zig:2:35: note: imported here 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 index d7673a637f1f..d901d5621a92 100644 --- a/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig +++ b/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f = @import("zon/leading_zero_in_integer.zon"); + const f: u128 = @import("zon/leading_zero_in_integer.zon"); _ = f; } diff --git a/test/cases/compile_errors/@import_zon_number_fail_limits.zig b/test/cases/compile_errors/@import_zon_number_fail_limits.zig index 18cb13ca9d05..9d04ce1b74ad 100644 --- a/test/cases/compile_errors/@import_zon_number_fail_limits.zig +++ b/test/cases/compile_errors/@import_zon_number_fail_limits.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/large_number.zon // -// 2:20: error: type 'i66' cannot represent integer value '36893488147419103232' +// 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_struct_dup_field.zig b/test/cases/compile_errors/@import_zon_struct_dup_field.zig index 22c66992e8ca..6ed9bcfe2a70 100644 --- a/test/cases/compile_errors/@import_zon_struct_dup_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -1,6 +1,6 @@ const std = @import("std"); pub fn main() void { - const f = @import("zon/struct_dup_field.zon"); + const f: struct { name: u8 } = @import("zon/struct_dup_field.zon"); _ = f; } @@ -10,3 +10,4 @@ pub fn main() void { // imports=zon/struct_dup_field.zon // // struct_dup_field.zon:3:6: error: duplicate field +// tmp.zig:3:44: note: imported here diff --git a/test/cases/compile_errors/@import_zon_unknown_ident.zig b/test/cases/compile_errors/@import_zon_unknown_ident.zig index 1b983e6053a5..28069fec2482 100644 --- a/test/cases/compile_errors/@import_zon_unknown_ident.zig +++ b/test/cases/compile_errors/@import_zon_unknown_ident.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/unknown_ident.zon"); + const f: struct { value: bool } = @import("zon/unknown_ident.zon"); _ = f; } @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/unknown_ident.zon // -// unknown_ident.zon:2:14: error: use of unknown identifier 'truefalse' +// unknown_ident.zon:2:14: error: expected bool +// tmp.zig:2:47: note: imported here From 16cbf53078bf8843c74ce99506fb7c379504a2b9 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 18:32:06 -0800 Subject: [PATCH 22/51] Fixes more failing compile error tests --- src/zon.zig | 61 +++++++++++++------ .../compile_errors/@import_zon_addr_slice.zig | 3 +- .../compile_errors/@import_zon_array_len.zig | 2 +- .../@import_zon_coerce_pointer.zig | 3 +- .../@import_zon_struct_dup_field.zig | 2 +- .../compile_errors/@import_zon_type_decl.zig | 3 +- .../@import_zon_type_expr_array.zig | 5 +- .../@import_zon_type_expr_fn.zig | 3 +- .../@import_zon_type_expr_struct.zig | 5 +- .../@import_zon_type_expr_tuple.zig | 5 +- .../@import_zon_type_mismatch.zig | 3 +- .../@import_zon_unescaped_newline.zig | 2 +- .../@import_zon_unknown_ident.zig | 2 +- 13 files changed, 64 insertions(+), 35 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index e89e0b492e14..4002f9de611b 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -283,7 +283,7 @@ fn lowerVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { return .void_value; } - return self.fail(.{ .node_abs = node }, "expected void", .{}); + return self.fail(.{ .node_abs = node }, "expected type 'void'", .{}); } fn lowerBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { @@ -308,7 +308,8 @@ fn lowerBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { }; } } - return self.fail(.{ .node_abs = node }, "expected bool", .{}); + const span = self.file.tree.nodeToSpan(node); + return self.fail(.{ .token_abs = span.start }, "expected type 'bool'", .{}); } fn lowerInt( @@ -506,7 +507,7 @@ fn lowerInt( .identifier => { unreachable; // Decide what error to give here }, - else => return self.fail(.{ .node_abs = num_lit_node }, "expected integer", .{}), + else => return self.fail(.{ .node_abs = num_lit_node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -665,7 +666,7 @@ fn lowerArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In const elem_nodes = try self.elements(res_ty, &buf, node); if (elem_nodes.len != array_info.len) { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(array_info.sentinel != null)); @@ -691,7 +692,7 @@ fn lowerEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Ind const ip = &self.sema.pt.zcu.intern_pool; if (tags[node] != .enum_literal) { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const field_name = try self.identAsNullTerminatedString(main_tokens[node]); @@ -714,7 +715,7 @@ fn lowerEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternP const gpa = self.sema.gpa; if (tags[node] != .enum_literal) { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } return ip.get(gpa, self.sema.pt.tid, .{ @@ -727,7 +728,7 @@ fn lowerStructOrTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !Inter return switch (ip.indexToKey(res_ty.toIntern())) { .tuple_type => self.lowerTuple(node, res_ty), .struct_type => self.lowerStruct(node, res_ty), - else => self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}), + else => self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; } @@ -788,7 +789,7 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I const name_index = struct_info.nameIndex(ip, field_name) orelse { return self.fail( .{ .node_abs = field_node }, - "unexpected field {}", + "unexpected field '{}'", .{field_name.fmt(ip)}, ); }; @@ -796,8 +797,8 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I const field_type = Type.fromInterned(struct_info.field_types.get(ip)[name_index]); if (field_values[name_index] != .none) { return self.fail( - .{ .node_abs = field_node }, - "duplicate field {}", + .{ .token_abs = field_name_token }, + "duplicate field '{}'", .{field_name.fmt(ip)}, ); } @@ -975,7 +976,7 @@ fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In var buf: [2]Ast.Node.Index = undefined; const field_nodes = try self.fields(res_ty, &buf, node); if (field_nodes.len > 1) { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const field_node = field_nodes[0]; const field_name_token = self.file.tree.firstToken(field_node) - 2; @@ -984,7 +985,7 @@ fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In }; const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { - return self.fail(.{ .node_abs = node }, "expected {}", .{res_ty.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); }; const tag_int = if (enum_tag_info.values.len == 0) b: { // Auto numbered fields @@ -1005,7 +1006,7 @@ fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.fail(.{ .node_abs = node }, "expected {}", .{field_type.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{field_type.fmt(self.sema.pt)}); } break :b .void_value; }; @@ -1024,22 +1025,34 @@ fn fields( ) ![]const NodeIndex { if (self.file.tree.fullStructInit(buf, node)) |init| { if (init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + return self.fail( + .{ .node_abs = init.ast.type_expr }, + "ZON cannot contain type expressions", + .{}, + ); } return init.ast.fields; } if (self.file.tree.fullArrayInit(buf, node)) |init| { if (init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + return self.fail( + .{ .node_abs = init.ast.type_expr }, + "ZON cannot contain type expressions", + .{}, + ); } if (init.ast.elements.len != 0) { - return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); + return self.fail( + .{ .node_abs = node }, + "expected type '{}'", + .{container.fmt(self.sema.pt)}, + ); } return init.ast.elements; } - return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{container.fmt(self.sema.pt)}); } fn elements( @@ -1050,21 +1063,29 @@ fn elements( ) ![]const NodeIndex { if (self.file.tree.fullArrayInit(buf, node)) |init| { if (init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + return self.fail( + .{ .node_abs = init.ast.type_expr }, + "ZON cannot contain type expressions", + .{}, + ); } return init.ast.elements; } if (self.file.tree.fullStructInit(buf, node)) |init| { if (init.ast.type_expr != 0) { - return self.fail(.{ .node_abs = node }, "ZON cannot contain type expressions", .{}); + return self.fail( + .{ .node_abs = init.ast.type_expr }, + "ZON cannot contain type expressions", + .{}, + ); } if (init.ast.fields.len == 0) { return init.ast.fields; } } - return self.fail(.{ .node_abs = node }, "expected {}", .{container.fmt(self.sema.pt)}); + return self.fail(.{ .node_abs = node }, "expected type '{}'", .{container.fmt(self.sema.pt)}); } fn createErrorWithOptionalNote( diff --git a/test/cases/compile_errors/@import_zon_addr_slice.zig b/test/cases/compile_errors/@import_zon_addr_slice.zig index 356a6878bb12..dca859e6581f 100644 --- a/test/cases/compile_errors/@import_zon_addr_slice.zig +++ b/test/cases/compile_errors/@import_zon_addr_slice.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/addr_slice.zon // -// addr_slice.zon:2:14: error: invalid ZON value +// addr_slice.zon:2:14: error: expected type '[]const i32' +// tmp.zig:2:54: note: imported here diff --git a/test/cases/compile_errors/@import_zon_array_len.zig b/test/cases/compile_errors/@import_zon_array_len.zig index 6ae6b381f43f..342504d5540b 100644 --- a/test/cases/compile_errors/@import_zon_array_len.zig +++ b/test/cases/compile_errors/@import_zon_array_len.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/array.zon // -// array.zon:1:2: error: expected [4]u8 +// 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_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig index 2a161863eb84..d4d0125eb2fb 100644 --- a/test/cases/compile_errors/@import_zon_coerce_pointer.zig +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/array.zon // -// found 'struct{comptime comptime_int = 97, comptime comptime_int = 98, comptime comptime_int = 99}' +// zon/array.zon:1:2: error: ZON import cannot be coerced to non slice pointer +// tmp.zig:2:47: 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 index 6ed9bcfe2a70..4ccc79a68733 100644 --- a/test/cases/compile_errors/@import_zon_struct_dup_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -9,5 +9,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/struct_dup_field.zon // -// struct_dup_field.zon:3:6: error: duplicate field +// struct_dup_field.zon:3:6: error: duplicate field name // tmp.zig:3:44: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_decl.zig b/test/cases/compile_errors/@import_zon_type_decl.zig index 4c7eebffca87..5d680249d2c0 100644 --- a/test/cases/compile_errors/@import_zon_type_decl.zig +++ b/test/cases/compile_errors/@import_zon_type_decl.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/type_decl.zon"); + const f: struct { foo: type } = @import("zon/type_decl.zon"); _ = f; } @@ -9,3 +9,4 @@ pub fn main() void { // imports=zon/type_decl.zon // // type_decl.zon:2:12: error: invalid ZON value +// tmp.zig:2:45: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_expr_array.zig b/test/cases/compile_errors/@import_zon_type_expr_array.zig index 2f57686e530f..994b986ea96e 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_array.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_array.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/type_expr_array.zon"); + const f: [3]i32 = @import("zon/type_expr_array.zon"); _ = f; } @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_array.zon // -// type_expr_array.zon:1:1: error: type expressions not allowed in ZON +// type_expr_array.zon:1:1: error: ZON cannot contain type expressions +// tmp.zig:2:31: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_expr_fn.zig b/test/cases/compile_errors/@import_zon_type_expr_fn.zig index 38c6a36867e1..79996d31877c 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_fn.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_fn.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_fn.zon // -// type_expr_fn.zon:1:1: error: type expressions not allowed in ZON +// type_expr_fn.zon:1:1: error: expected type 'i8' +// tmp.zig:2:28: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_expr_struct.zig b/test/cases/compile_errors/@import_zon_type_expr_struct.zig index 194baa2057ba..bedc9ea37715 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_struct.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_struct.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/type_expr_struct.zon"); + const f: struct { x: f32, y: f32 } = @import("zon/type_expr_struct.zon"); _ = f; } @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_struct.zon // -// type_expr_struct.zon:1:1: error: type expressions not allowed in ZON +// type_expr_struct.zon:1:1: error: ZON cannot contain type expressions +// tmp.zig:2:50: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig index 501ed5395142..53bb497ff7de 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/type_expr_tuple.zon"); + const f: struct { f32, f32 } = @import("zon/type_expr_tuple.zon"); _ = f; } @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_tuple.zon // -// type_expr_tuple.zon:1:1: error: type expressions not allowed in ZON +// type_expr_tuple.zon:1:1: error: ZON cannot contain type expressions +// tmp.zig:2:44: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_mismatch.zig b/test/cases/compile_errors/@import_zon_type_mismatch.zig index 5d42fab9b9f8..467853168718 100644 --- a/test/cases/compile_errors/@import_zon_type_mismatch.zig +++ b/test/cases/compile_errors/@import_zon_type_mismatch.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/struct.zon // -// 2:21: error: expected type 'bool', found 'struct{comptime boolean: bool = true, comptime number: comptime_int = 123}' +// struct.zon:1:1: 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 index d6e188f77c6b..a342802ecb54 100644 --- a/test/cases/compile_errors/@import_zon_unescaped_newline.zig +++ b/test/cases/compile_errors/@import_zon_unescaped_newline.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/unescaped_newline.zon // -// unescaped_newline.zon:1:1: error: expected expression, found 'invalid bytes' +// unescaped_newline.zon:1:1: error: expected expression, found 'invalid token' // unescaped_newline.zon:1:3: note: invalid byte: '\n' diff --git a/test/cases/compile_errors/@import_zon_unknown_ident.zig b/test/cases/compile_errors/@import_zon_unknown_ident.zig index 28069fec2482..cbc37f3a7623 100644 --- a/test/cases/compile_errors/@import_zon_unknown_ident.zig +++ b/test/cases/compile_errors/@import_zon_unknown_ident.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/unknown_ident.zon // -// unknown_ident.zon:2:14: error: expected bool +// unknown_ident.zon:2:14: error: expected type 'bool' // tmp.zig:2:47: note: imported here From 0e165ba8fd0e86c768abcdd299a789df1c99861c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 18:49:00 -0800 Subject: [PATCH 23/51] Fixes more zon compile error tests --- src/zon.zig | 7 +++---- .../compile_errors/@import_zon_double_negation_float.zig | 4 ++-- .../compile_errors/@import_zon_double_negation_int.zig | 3 ++- test/cases/compile_errors/@import_zon_struct_dup_field.zig | 2 +- test/cases/compile_errors/@import_zon_type_expr_fn.zig | 2 +- test/cases/compile_errors/@import_zon_type_mismatch.zig | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 4002f9de611b..0caa6c22994a 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -308,8 +308,7 @@ fn lowerBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { }; } } - const span = self.file.tree.nodeToSpan(node); - return self.fail(.{ .token_abs = span.start }, "expected type 'bool'", .{}); + return self.fail(.{ .node_abs = node }, "expected type 'bool'", .{}); } fn lowerInt( @@ -507,7 +506,7 @@ fn lowerInt( .identifier => { unreachable; // Decide what error to give here }, - else => return self.fail(.{ .node_abs = num_lit_node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -625,7 +624,7 @@ fn lowerFloat( } return self.fail(.{ .node_abs = num_lit_node }, "use of unknown identifier '{s}'", .{bytes}); }, - else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), + else => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), } } diff --git a/test/cases/compile_errors/@import_zon_double_negation_float.zig b/test/cases/compile_errors/@import_zon_double_negation_float.zig index def6c8613546..fdcbe5138ca2 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_float.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_float.zig @@ -1,5 +1,5 @@ pub fn main() void { - const f: i32 = @import("zon/double_negation_float.zon"); + const f: f32 = @import("zon/double_negation_float.zon"); _ = f; } @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/double_negation_float.zon // -// double_negation_float.zon:1:2: error: invalid ZON value +// double_negation_float.zon:1:1: error: invalid ZON value // tmp.zig:2:28: note: imported here diff --git a/test/cases/compile_errors/@import_zon_double_negation_int.zig b/test/cases/compile_errors/@import_zon_double_negation_int.zig index ae04a8e8170b..2201b09fdda6 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_int.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_int.zig @@ -8,4 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/double_negation_int.zon // -// double_negation_int.zon:1:2: error: invalid ZON value +// double_negation_int.zon:1:1: error: expected type 'i32' +// tmp.zig:2:28: note: imported here diff --git a/test/cases/compile_errors/@import_zon_struct_dup_field.zig b/test/cases/compile_errors/@import_zon_struct_dup_field.zig index 4ccc79a68733..fa35aa268fb0 100644 --- a/test/cases/compile_errors/@import_zon_struct_dup_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -9,5 +9,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/struct_dup_field.zon // -// struct_dup_field.zon:3:6: error: duplicate field name +// struct_dup_field.zon:3:6: error: duplicate field 'name' // tmp.zig:3:44: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_expr_fn.zig b/test/cases/compile_errors/@import_zon_type_expr_fn.zig index 79996d31877c..dfc012339ffd 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_fn.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_fn.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_fn.zon // -// type_expr_fn.zon:1:1: error: expected type 'i8' +// type_expr_fn.zon:1:15: error: expected type 'i32' // tmp.zig:2:28: note: imported here diff --git a/test/cases/compile_errors/@import_zon_type_mismatch.zig b/test/cases/compile_errors/@import_zon_type_mismatch.zig index 467853168718..530ee5c147a6 100644 --- a/test/cases/compile_errors/@import_zon_type_mismatch.zig +++ b/test/cases/compile_errors/@import_zon_type_mismatch.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/struct.zon // -// struct.zon:1:1: error: expected type 'bool' +// struct.zon:1:2: error: expected type 'bool' // tmp.zig:2:29: note: imported here From 64725c8bb1e5f370423c664ec98f0f4c712df69c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 19:20:54 -0800 Subject: [PATCH 24/51] Gets all ZON tests passing locally --- lib/std/zig/string_literal.zig | 18 ++++++++++++++---- .../@import_zon_negative_zero.zig | 1 + .../@import_zon_negative_zero_cast_float.zig | 11 ----------- 3 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index cfa4a103f62e..e0de43f19590 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -427,18 +427,22 @@ pub fn MultilineParser(comptime Writer: type) type { }; } - /// Parse one line of a multiline string, writing the result to the writer prepending a newline if necessary. + /// Parse one line of a multiline string, writing the result to the writer prepending a + /// newline if necessary. /// - /// Asserts bytes begins with "\\". The line may be terminated with '\n' or "\r\n", but may not contain any interior newlines. - /// contain any interior newlines. + /// Asserts bytes begins with "\\". The line may be terminated with '\n' or "\r\n", but may + /// not contain any interior newlines. pub fn line(self: *@This(), bytes: []const u8) Writer.Error!void { assert(bytes.len >= 2 and bytes[0] == '\\' and bytes[1] == '\\'); + var terminator_len: usize = 0; + terminator_len += @intFromBool(bytes[bytes.len - 1] == '\n'); + terminator_len += @intFromBool(bytes[bytes.len - 2] == '\r'); if (self.first_line) { self.first_line = false; } else { try self.writer.writeByte('\n'); } - try self.writer.writeAll(bytes[2..]); + try self.writer.writeAll(bytes[2 .. bytes.len - terminator_len]); } }; } @@ -462,12 +466,18 @@ test "parse multiline" { } { + const temp = + \\foo + \\bar + ; + try std.testing.expectEqualStrings("foo\nbar", temp); var parsed = std.ArrayList(u8).init(std.testing.allocator); defer parsed.deinit(); const writer = parsed.writer(); var parser = multilineParser(writer); try parser.line("\\\\foo"); try std.testing.expectEqualStrings("foo", parsed.items); + // XXX: this adds the newline but like...does the input ever actually have a newline there? try parser.line("\\\\bar\n"); try std.testing.expectEqualStrings("foo\nbar", parsed.items); } diff --git a/test/cases/compile_errors/@import_zon_negative_zero.zig b/test/cases/compile_errors/@import_zon_negative_zero.zig index fc67b074842a..5e935098a066 100644 --- a/test/cases/compile_errors/@import_zon_negative_zero.zig +++ b/test/cases/compile_errors/@import_zon_negative_zero.zig @@ -9,3 +9,4 @@ pub fn main() void { // imports=zon/negative_zero.zon // // negative_zero.zon:1:1: error: integer literal '-0' is ambiguous +// tmp.zig:2:27: note: imported here diff --git a/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig b/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig deleted file mode 100644 index beb2e9bfc595..000000000000 --- a/test/cases/compile_errors/@import_zon_negative_zero_cast_float.zig +++ /dev/null @@ -1,11 +0,0 @@ -pub fn main() void { - const f: f32 = @import("zon/negative_zero.zon"); - _ = f; -} - -// error -// backend=stage2 -// output_mode=Exe -// imports=zon/negative_zero.zon -// -// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous From 405b8f2a855fc3f703b01f0d7ec63736d4d99f44 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 23:35:57 -0800 Subject: [PATCH 25/51] Removes platform specific separator from test case --- test/cases/compile_errors/@import_zon_coerce_pointer.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig index d4d0125eb2fb..fc1b94639f60 100644 --- a/test/cases/compile_errors/@import_zon_coerce_pointer.zig +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/array.zon // -// zon/array.zon:1:2: error: ZON import cannot be coerced to non slice pointer +// array.zon:1:2: error: ZON import cannot be coerced to non slice pointer // tmp.zig:2:47: note: imported here From ed8ae5ed4d37d729b1be5b408c17c06ebd603880 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 17 Dec 2024 16:11:17 -0800 Subject: [PATCH 26/51] Fixes mistake in rebase --- lib/std/zig/string_literal.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index e0de43f19590..daba9b147f39 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -43,7 +43,7 @@ pub const Error = union(enum) { pub fn lower( err: Error, raw_string: []const u8, - offset: u32, + off: u32, comptime func: anytype, first_args: anytype, ) @typeInfo(@TypeOf(func)).@"fn".return_type.? { @@ -66,7 +66,7 @@ pub const Error = union(enum) { .empty_char_literal => .{ "empty character literal", .{} }, }; return @call(.auto, func, first_args ++ .{ - offset + bad_index, + off + bad_index, fmt_str, args, }); From 49614458dfe766d6d899a78084b4f81e69f0b94c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 17 Dec 2024 20:36:34 -0800 Subject: [PATCH 27/51] Updates runtime parser to use ZonGen --- lib/std/zig/ZonGen.zig | 2 +- lib/std/zon/parse.zig | 2718 ++++++++++++++++++---------------------- 2 files changed, 1192 insertions(+), 1528 deletions(-) diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 7f85f35f0512..09467da51ca0 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -540,7 +540,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; } diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index ecc783994ff6..d1a618bde242 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1,7 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Ast = std.zig.Ast; -const NodeIndex = std.zig.Ast.Node.Index; +const Zoir = std.zig.Zoir; +const ZonGen = std.zig.ZonGen; const TokenIndex = std.zig.Ast.TokenIndex; const Base = std.zig.number_literal.Base; const StringLiteralError = std.zig.string_literal.Error; @@ -10,9 +11,9 @@ const assert = std.debug.assert; const ArrayListUnmanaged = std.ArrayListUnmanaged; gpa: Allocator, -ast: *const Ast, -status: ?*ParseStatus, -ident_buf: []u8, +ast: Ast, +zoir: Zoir, +status: ?*Status, /// Configuration for the runtime parser. pub const ParseOptions = struct { @@ -25,209 +26,295 @@ pub const ParseOptions = struct { }; /// Information about the success or failure of a parse. -pub const ParseStatus = union { - success: void, - failure: ParseFailure, -}; +pub const Status = struct { + pub const TypeCheckError = struct { + token: Ast.TokenIndex, + message: []const u8, + }; -/// Information about a parse failure for presentation to the user via the format functions. -pub const ParseFailure = struct { - ast: *const Ast, - token: TokenIndex, - reason: Reason, - - const Reason = union(enum) { - out_of_memory: void, - expected_union: void, - expected_struct: void, - expected_primitive: struct { type_name: []const u8 }, - expected_enum: void, - expected_tuple_with_fields: struct { - fields: usize, - }, - expected_tuple: void, - expected_string: void, - cannot_represent: struct { type_name: []const u8 }, - negative_integer_zero: void, - invalid_string_literal: struct { - err: StringLiteralError, + pub const Error = union(enum) { + pub const Severity = enum { + @"error", + note, + }; + parse: struct { + ast: Ast, + err: Ast.Error, }, - invalid_number_literal: struct { - err: NumberLiteralError, + zon_gen_err: struct { + zoir: Zoir, + err: Zoir.CompileError, + ast: Ast, }, - unexpected_field: struct { - fields: []const []const u8, + zon_gen_note: struct { + zoir: Zoir, + err: Zoir.CompileError.Note, + ast: Ast, }, - missing_field: struct { - field_name: []const u8, + type_check: struct { + err: TypeCheckError, + ast: Ast, }, - duplicate_field: void, - type_expr: void, - address_of: void, - }; - - pub fn fmtLocation(self: *const @This()) std.fmt.Formatter(formatLocation) { - return .{ .data = self }; - } - fn formatLocation( - self: *const @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = fmt; - const l = self.ast.tokenLocation(0, self.token); - const offset = switch (self.reason) { - .invalid_string_literal => |r| r.err.offset(), - .invalid_number_literal => |r| r.err.offset(), - else => 0, + pub const Iterator = union(enum) { + parse: struct { + ast: Ast, + err_index: usize = 0, + }, + zon_gen: struct { + zoir: Zoir, + ast: Ast, + err_index: usize = 0, + note_index: ?usize = null, + }, + type_check: struct { + err: TypeCheckError, + err_index: usize = 0, + ast: Ast, + }, + none, + + pub fn next(self: *@This()) ?Error { + switch (self.*) { + .parse => |*iter| { + if (iter.err_index >= iter.ast.errors.len) return null; + const curr = iter.err_index; + iter.err_index += 1; + return .{ .parse = .{ + .ast = iter.ast, + .err = iter.ast.errors[curr], + } }; + }, + .zon_gen => |*iter| { + if (iter.err_index >= iter.zoir.compile_errors.len) return null; + const err = iter.zoir.compile_errors[iter.err_index]; + + // If we're iterating notes for an error, try to return the next one. If + // there are no more recurse and try the next error. + if (iter.note_index) |*note_index| { + if (note_index.* < err.note_count) { + const note = err.getNotes(iter.zoir)[note_index.*]; + note_index.* += 1; + return .{ .zon_gen_note = .{ + .err = note, + .zoir = iter.zoir, + .ast = iter.ast, + } }; + } else { + iter.note_index = null; + iter.err_index += 1; + return self.next(); + } + } + + // Return the next error, next time try returning notes. + iter.note_index = 0; + return .{ .zon_gen_err = .{ + .zoir = iter.zoir, + .err = err, + .ast = iter.ast, + } }; + }, + .type_check => |*iter| { + if (iter.err_index > 0) return null; + iter.err_index += 1; + return .{ .type_check = .{ + .err = iter.err, + .ast = iter.ast, + } }; + }, + .none => return null, + } + } }; - try writer.print("{}:{}", .{ l.line + 1, l.column + 1 + offset }); - } - pub fn fmtError(self: *const @This()) std.fmt.Formatter(formatError) { - return .{ .data = self }; - } + pub fn getSeverity(self: @This()) Severity { + return switch (self) { + .parse => |kind| if (kind.err.is_note) .note else .@"error", + .zon_gen_err => .@"error", + .zon_gen_note => .note, + .type_check => .@"error", + }; + } - fn formatError( - self: *const @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = fmt; - return switch (self.reason) { - .out_of_memory => writer.writeAll("out of memory"), - .expected_union => writer.writeAll("expected union"), - .expected_struct => writer.writeAll("expected struct"), - .expected_primitive => |r| writer.print("expected {s}", .{r.type_name}), - .expected_enum => writer.writeAll("expected enum literal"), - .expected_tuple_with_fields => |r| { - const plural = if (r.fields == 1) "" else "s"; - try writer.print("expected tuple with {} field{s}", .{ r.fields, plural }); - }, - .expected_tuple => writer.writeAll("expected tuple"), - .expected_string => writer.writeAll("expected string"), - .cannot_represent => |r| writer.print("{s} cannot represent value", .{r.type_name}), - .negative_integer_zero => writer.writeAll("integer literal '-0' is ambiguous"), - .invalid_string_literal => |r| writer.print("{}", .{r.err.fmtWithSource(self.ast.tokenSlice(self.token))}), - .invalid_number_literal => |r| writer.print("{}", .{r.err.fmtWithSource(self.ast.tokenSlice(self.token))}), - .unexpected_field => |r| { - try writer.writeAll("unexpected field, "); - if (r.fields.len == 0) { - try writer.writeAll("no fields expected"); - } else { - try writer.writeAll("supported fields: "); - for (0..r.fields.len) |i| { - if (i != 0) try writer.writeAll(", "); - try writer.print("{}", .{std.zig.fmtId(r.fields[i])}); + pub fn getLocation(self: @This()) Ast.Location { + switch (self) { + .parse => |kind| { + const offset = kind.ast.errorOffset(kind.err); + return kind.ast.tokenLocation(offset, kind.err.token); + }, + inline .zon_gen_err, .zon_gen_note => |kind| { + if (kind.err.token == Zoir.CompileError.invalid_token) { + const main_tokens = kind.ast.nodes.items(.main_token); + const ast_node = kind.err.node_or_offset; + const token = main_tokens[ast_node]; + return kind.ast.tokenLocation(0, token); + } else { + var location = kind.ast.tokenLocation(0, kind.err.token); + location.column += kind.err.node_or_offset; + return location; } - } - }, - .missing_field => |r| writer.print("missing required field {s}", .{r.field_name}), - .duplicate_field => writer.writeAll("duplicate field"), - .type_expr => writer.writeAll("ZON cannot contain type expressions"), - .address_of => writer.writeAll("ZON cannot take the address of a value"), - }; - } + }, + .type_check => |kind| return kind.ast.tokenLocation(0, kind.err.token), + } + } - pub fn noteCount(self: *const @This()) usize { - switch (self.reason) { - .invalid_number_literal => |r| { - const source = self.ast.tokenSlice(self.token); - return if (r.err.noteWithSource(source) != null) 1 else 0; - }, - else => return 0, + pub fn fmtMessage(self: @This()) std.fmt.Formatter(formatMessage) { + return .{ .data = self }; } - } - const FormatNote = struct { - failure: *const ParseFailure, - index: usize, + fn formatMessage( + self: @This(), + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + _ = fmt; + switch (self) { + .parse => |kind| try kind.ast.renderError(kind.err, writer), + inline .zon_gen_err, .zon_gen_note => |kind| { + try writer.writeAll(kind.err.msg.get(kind.zoir)); + }, + .type_check => |kind| try writer.writeAll(kind.err.message), + } + } }; - pub fn fmtNote(self: *const @This(), index: usize) std.fmt.Formatter(formatNote) { - return .{ .data = .{ .failure = self, .index = index } }; + /// The AST, which may or may not contain errors. + ast: ?Ast = null, + /// The Zoir, which may or may not contain errors. + zoir: ?Zoir = null, + /// The type check error if one occurred. + type_check: ?TypeCheckError = null, + + fn assertEmpty(self: Status) void { + assert(self.ast == null); + assert(self.zoir == null); + assert(self.type_check == null); } - fn formatNote( - self: FormatNote, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = fmt; - switch (self.failure.reason) { - .invalid_number_literal => |r| { - std.debug.assert(self.index == 0); - const source = self.failure.ast.tokenSlice(self.failure.token); - try writer.writeAll(r.err.noteWithSource(source).?); - return; - }, - else => {}, + pub fn deinit(self: *Status, gpa: Allocator) void { + if (self.ast) |*ast| ast.deinit(gpa); + if (self.zoir) |*zoir| zoir.deinit(gpa); + self.* = undefined; + } + + pub fn iterateErrors(self: *const Status) Error.Iterator { + const ast = self.ast orelse return .none; + + if (ast.errors.len > 0) { + return .{ .parse = .{ + .ast = ast, + } }; + } + + if (self.zoir) |zoir| { + if (zoir.hasCompileErrors()) { + return .{ .zon_gen = .{ + .zoir = zoir, + .ast = ast, + } }; + } + } + + if (self.type_check) |type_check| { + return .{ .type_check = .{ + .err = type_check, + .ast = ast, + } }; } - unreachable; + return .none; } pub fn format( - self: @This(), + self: *const @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; - try writer.print("{}: {}", .{ self.fmtLocation(), self.fmtError() }); + + var first = true; + var errors = self.iterateErrors(); + while (errors.next()) |err| { + if (!first) { + try writer.writeByte('\n'); + } else { + first = false; + } + const loc = err.getLocation(); + const msg = err.fmtMessage(); + const severity = @tagName(err.getSeverity()); + try writer.print("{}:{}: {s}: {}", .{ loc.line + 1, loc.column + 1, severity, msg }); + } } }; -test "std.zon failure/oom formatting" { +test "std.zon ast errors" { + // Test multiple errors const gpa = std.testing.allocator; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{})); + try std.testing.expectFmt( + \\1:13: error: expected ',' after initializer + \\1:13: error: expected field initializer + , "{}", .{status}); +} - // Generate a failure - var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); - defer ast.deinit(gpa); +test "std.zon comments" { + const gpa = std.testing.allocator; + + try std.testing.expectEqual(@as(u8, 10), parseFromSlice(u8, gpa, + \\// comment + \\10 // comment + \\// comment + , null, .{})); + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, + \\//! comment + \\10 // comment + \\// comment + , &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected expression, found 'a document comment'", + "{}", + .{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: ParseStatus = undefined; - try std.testing.expectError(error.OutOfMemory, parseFromAst( + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.OutOfMemory, parseFromSlice( []const u8, failing_allocator.allocator(), - &ast, + "\"foo\"", &status, .{}, )); - - // Verify that we can format the entire failure. - const full = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(full); - try std.testing.expectEqualStrings("1:1: out of memory", full); - try std.testing.expectEqual(0, status.failure.noteCount()); - - // Verify that we can format the location by itself - const location = try std.fmt.allocPrint(gpa, "{}", .{status.failure.fmtLocation()}); - defer gpa.free(location); - try std.testing.expectEqualStrings("1:1", location); - - // Verify that we can format the reason by itself - const reason = try std.fmt.allocPrint(gpa, "{}", .{status.failure.fmtError()}); - defer std.testing.allocator.free(reason); - try std.testing.expectEqualStrings("out of memory", reason); + try std.testing.expectFmt("", "{}", .{status}); } -/// Parses the given ZON source. +/// Parses the given slice as ZON. /// -/// Returns `error.OutOfMemory` on allocator failure, a `error.Type` error if the ZON could not be -/// deserialized into `T`, or `error.Syntax` error if the ZON was invalid. +/// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is +/// invalid or can not be deserialized into type `T`. /// -/// If detailed failure information is needed, see `parseFromAst`. +/// 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 parseFromSlice( /// The type to deserialize into. May only transitively contain the following supported types: /// * bools @@ -240,118 +327,129 @@ pub fn parseFromSlice( /// * optionals /// * null comptime T: type, - /// The allocator. Used to temporarily allocate an AST, and to allocate any parts of `T` that - /// require dynamic allocation. gpa: Allocator, - /// The ZON source. source: [:0]const u8, - /// Options for the parser. + status: ?*Status, comptime options: ParseOptions, -) error{ OutOfMemory, Type, Syntax }!T { - if (@inComptime()) { - // Happens if given e.g. @typeOf(null), the default error we get is hard - // to understand. - @compileError("Runtime parser cannot run at comptime."); - } +) error{ OutOfMemory, ParseZon }!T { + if (status) |s| s.assertEmpty(); + var ast = try std.zig.Ast.parse(gpa, source, .zon); - defer ast.deinit(gpa); - if (ast.errors.len != 0) return error.Syntax; - return parseFromAst(T, gpa, &ast, null, options); + defer if (status == null) ast.deinit(gpa); + if (status) |s| s.ast = ast; + if (ast.errors.len != 0) return error.ParseZon; + + var zoir = try ZonGen.generate(gpa, ast); + defer if (status == null) zoir.deinit(gpa); + if (status) |s| s.zoir = zoir; + if (zoir.hasCompileErrors()) return error.ParseZon; + + if (status) |s| s.* = .{}; + return parseFromZoir(T, gpa, ast, zoir, status, options); } test "std.zon parseFromSlice syntax error" { - try std.testing.expectError(error.Syntax, parseFromSlice(u8, std.testing.allocator, ".{", .{})); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, std.testing.allocator, ".{", null, .{})); } -/// Like `parseFromSlice`, but operates on an AST instead of on ZON source. Asserts that the AST's -/// `errors` field is empty. -/// -/// Returns `error.OutOfMemory` if allocation fails, or `error.Type` if the ZON could not be -/// deserialized into `T`. -/// -/// If `status` is not null, its success field will be set on success, and its failure field will be -/// set on failure. See `ParseFailure` for formatting ZON parse failures in a human readable -/// manner. For formatting AST errors, see `std.zig.Ast.renderError`. -pub fn parseFromAst(comptime T: type, gpa: Allocator, ast: *const Ast, status: ?*ParseStatus, comptime options: ParseOptions) error{ OutOfMemory, Type }!T { - assert(ast.errors.len == 0); - const data = ast.nodes.items(.data); - const root = data[0].lhs; - return parseFromAstNode(T, gpa, ast, root, status, options); +/// Like `parseFromSlice`, but operates on `Zoir` instead of ZON source. +pub fn parseFromZoir( + comptime T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + comptime options: ParseOptions, +) error{ OutOfMemory, ParseZon }!T { + return parseFromZoirNode(T, gpa, ast, zoir, .root, status, options); } -/// Like `parseFromAst`, but does not take an allocator. +/// Like `parseFromZoir`, but does not take an allocator. /// -/// Asserts at comptime that no value of type `T` requires dynamic allocation. -pub fn parseFromAstNoAlloc(comptime T: type, ast: *const Ast, status: ?*ParseStatus, comptime options: ParseOptions) error{Type}!T { - assert(ast.errors.len == 0); - const data = ast.nodes.items(.data); - const root = data[0].lhs; - return parseFromAstNodeNoAlloc(T, ast, root, status, options); +/// Asserts at comptime that no values of `T` require dynamic allocation. +pub fn parseFromZoirNoAlloc( + comptime T: type, + ast: Ast, + zoir: Zoir, + status: ?*Status, + comptime options: ParseOptions, +) error{ParseZon}!T { + return parseFromZoirNodeNoAlloc(T, ast, zoir, .root, status, options); } -test "std.zon parseFromAstNoAlloc" { +test "std.zon parseFromZoirNoAlloc" { var ast = try std.zig.Ast.parse(std.testing.allocator, ".{ .x = 1.5, .y = 2.5 }", .zon); defer ast.deinit(std.testing.allocator); - try std.testing.expectEqual(ast.errors.len, 0); - + var zoir = try ZonGen.generate(std.testing.allocator, ast); + defer zoir.deinit(std.testing.allocator); const S = struct { x: f32, y: f32 }; - const found = try parseFromAstNoAlloc(S, &ast, null, .{}); + const found = try parseFromZoirNoAlloc(S, ast, zoir, null, .{}); try std.testing.expectEqual(S{ .x = 1.5, .y = 2.5 }, found); } -/// Like `parseFromAst`, but the parse starts on `node` instead of on the root of the AST. -pub fn parseFromAstNode(comptime T: type, gpa: Allocator, ast: *const Ast, node: NodeIndex, status: ?*ParseStatus, comptime options: ParseOptions) error{ OutOfMemory, Type }!T { - assert(ast.errors.len == 0); - var ident_buf: [maxIdentLength(T)]u8 = undefined; +/// Like `parseFromZoir`, but the parse starts on `node` instead of root. +pub fn parseFromZoirNode( + comptime T: type, + gpa: Allocator, + ast: Ast, + zoir: Zoir, + node: Zoir.Node.Index, + status: ?*Status, + comptime options: ParseOptions, +) error{ OutOfMemory, ParseZon }!T { + if (status) |s| { + s.assertEmpty(); + s.ast = ast; + s.zoir = zoir; + } + + if (zoir.hasCompileErrors() or ast.errors.len > 0) { + return error.ParseZon; + } + var parser = @This(){ .gpa = gpa, .ast = ast, + .zoir = zoir, .status = status, - .ident_buf = &ident_buf, - }; - - // Attempt the parse, setting status and returning early if it fails - const result = parser.parseExpr(T, options, node) catch |err| switch (err) { - error.ParserOutOfMemory => return error.OutOfMemory, - error.Type => return error.Type, }; - // Set status to success and return the result - if (status) |s| s.* = .{ .success = {} }; - return result; + return parser.parseExpr(T, options, node); } -/// Like `parseFromAstNode`, but does not take an allocator. -/// -/// Asserts at comptime that no value of type `T` requires dynamic allocation. -pub fn parseFromAstNodeNoAlloc(comptime T: type, ast: *const Ast, node: NodeIndex, status: ?*ParseStatus, comptime options: ParseOptions) error{Type}!T { - assert(ast.errors.len == 0); +/// See `parseFromZoirNoAlloc` and `parseFromZoirNode`. +pub fn parseFromZoirNodeNoAlloc( + comptime T: type, + ast: Ast, + zoir: Zoir, + node: Zoir.Node.Index, + status: ?*Status, + comptime options: ParseOptions, +) error{ParseZon}!T { if (comptime requiresAllocator(T)) { @compileError(@typeName(T) ++ ": requires allocator"); } var buffer: [0]u8 = .{}; var fba = std.heap.FixedBufferAllocator.init(&buffer); - return parseFromAstNode(T, fba.allocator(), ast, node, status, options) catch |e| switch (e) { + return parseFromZoirNode(T, fba.allocator(), ast, zoir, node, status, options) catch |e| switch (e) { error.OutOfMemory => unreachable, // No allocations else => |other| return other, }; } -test "std.zon parseFromAstNode and parseFromAstNodeNoAlloc" { +test "std.zon parseFromZoirNode and parseFromZoirNodeNoAlloc" { const gpa = std.testing.allocator; var ast = try std.zig.Ast.parse(gpa, ".{ .vec = .{ .x = 1.5, .y = 2.5 } }", .zon); defer ast.deinit(gpa); - try std.testing.expect(ast.errors.len == 0); + var zoir = try ZonGen.generate(gpa, ast); + defer zoir.deinit(gpa); - const data = ast.nodes.items(.data); - const root = data[0].lhs; - var buf: [2]NodeIndex = undefined; - const init = ast.fullStructInit(&buf, root).?; + const vec = Zoir.Node.Index.root.get(zoir).struct_literal.vals.at(0); const Vec2 = struct { x: f32, y: f32 }; - const parsed = try parseFromAstNode(Vec2, gpa, &ast, init.ast.fields[0], null, .{}); - const parsed_no_alloc = try parseFromAstNodeNoAlloc(Vec2, &ast, init.ast.fields[0], null, .{}); + const parsed = try parseFromZoirNode(Vec2, gpa, ast, zoir, vec, null, .{}); + const parsed_no_alloc = try parseFromZoirNodeNoAlloc(Vec2, ast, zoir, vec, null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed); try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed_no_alloc); } @@ -398,96 +496,13 @@ test "std.zon requiresAllocator" { try std.testing.expect(requiresAllocator(?[]u8)); } -fn maxIdentLength(comptime T: type) usize { - // Keep in sync with `parseExpr`. - comptime var max = 0; - switch (@typeInfo(T)) { - .bool, .int, .float, .null, .void => {}, - .pointer => |pointer| max = comptime maxIdentLength(pointer.child), - .array => |array| if (array.len > 0) { - max = comptime maxIdentLength(array.child); - }, - .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { - if (!@"struct".is_tuple) { - max = @max(max, field.name.len); - } - max = @max(max, comptime maxIdentLength(field.type)); - }, - .@"union" => |@"union"| inline for (@"union".fields) |field| { - max = @max(max, field.name.len); - max = @max(max, comptime maxIdentLength(field.type)); - }, - .@"enum" => |@"enum"| inline for (@"enum".fields) |field| { - max = @max(max, field.name.len); - }, - .optional => |optional| max = comptime maxIdentLength(optional.child), - else => unreachable, - } - return max; -} - -test "std.zon maxIdentLength" { - // Primitives - try std.testing.expectEqual(0, maxIdentLength(bool)); - try std.testing.expectEqual(0, maxIdentLength(u8)); - try std.testing.expectEqual(0, maxIdentLength(f32)); - try std.testing.expectEqual(0, maxIdentLength(@TypeOf(null))); - try std.testing.expectEqual(0, maxIdentLength(void)); - - // Arrays - try std.testing.expectEqual(0, maxIdentLength([0]u8)); - try std.testing.expectEqual(0, maxIdentLength([5]u8)); - try std.testing.expectEqual(3, maxIdentLength([5]struct { abc: f32 })); - try std.testing.expectEqual(0, maxIdentLength([0]struct { abc: f32 })); - - // Structs - try std.testing.expectEqual(0, maxIdentLength(struct {})); - try std.testing.expectEqual(1, maxIdentLength(struct { a: f32, b: f32 })); - try std.testing.expectEqual(3, maxIdentLength(struct { abc: f32, a: f32 })); - try std.testing.expectEqual(3, maxIdentLength(struct { a: f32, abc: f32 })); - - try std.testing.expectEqual(1, maxIdentLength(struct { a: struct { a: f32 }, b: struct { a: f32 } })); - try std.testing.expectEqual(3, maxIdentLength(struct { a: struct { abc: f32 }, b: struct { a: f32 } })); - try std.testing.expectEqual(3, maxIdentLength(struct { a: struct { a: f32 }, b: struct { abc: f32 } })); - - // Tuples - try std.testing.expectEqual(0, maxIdentLength(struct { f32, u32 })); - try std.testing.expectEqual(3, maxIdentLength(struct { struct { a: u32 }, struct { abc: u32 } })); - try std.testing.expectEqual(3, maxIdentLength(struct { struct { abc: u32 }, struct { a: u32 } })); - - // Unions - try std.testing.expectEqual(0, maxIdentLength(union {})); - - try std.testing.expectEqual(1, maxIdentLength(union { a: f32, b: f32 })); - try std.testing.expectEqual(3, maxIdentLength(union { abc: f32, a: f32 })); - try std.testing.expectEqual(3, maxIdentLength(union { a: f32, abc: f32 })); - - try std.testing.expectEqual(1, maxIdentLength(union { a: union { a: f32 }, b: union { a: f32 } })); - try std.testing.expectEqual(3, maxIdentLength(union { a: union { abc: f32 }, b: union { a: f32 } })); - try std.testing.expectEqual(3, maxIdentLength(union { a: union { a: f32 }, b: union { abc: f32 } })); - - // Enums - try std.testing.expectEqual(0, maxIdentLength(enum {})); - try std.testing.expectEqual(3, maxIdentLength(enum { a, abc })); - try std.testing.expectEqual(3, maxIdentLength(enum { abc, a })); - try std.testing.expectEqual(1, maxIdentLength(enum { a, b })); - - // Optionals - try std.testing.expectEqual(0, maxIdentLength(?u32)); - try std.testing.expectEqual(3, maxIdentLength(?struct { abc: u32 })); - - // Pointers - try std.testing.expectEqual(0, maxIdentLength(*u32)); - try std.testing.expectEqual(3, maxIdentLength(*struct { abc: u32 })); -} - -/// Frees values created by the runtime parser. +/// 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 to free this type of value. -/// Untagged unions, for example, can be parsed but not freed. +/// 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 parseFree(gpa: Allocator, value: anytype) void { const Value = @TypeOf(value); @@ -533,21 +548,13 @@ fn parseExpr( self: *@This(), comptime T: type, comptime options: ParseOptions, - node: NodeIndex, -) error{ ParserOutOfMemory, Type }!T { - // Check for address of up front so we can emit a friendlier error (otherwise it will just say - // that the type is wrong, which may be confusing.) - const tags = self.ast.nodes.items(.tag); - if (tags[node] == .address_of) { - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - return self.fail(token, .address_of); - } - + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { // Keep in sync with parseFree, stringify, and requiresAllocator. switch (@typeInfo(T)) { .bool => return self.parseBool(node), - .int, .float => return self.parseNumber(T, node), + .int => return self.parseInt(T, node), + .float => return self.parseFloat(T, node), .@"enum" => return self.parseEnumLiteral(T, node), .pointer => return self.parsePointer(T, options, node), .array => return self.parseArray(T, options, node), @@ -557,64 +564,22 @@ fn parseExpr( return self.parseStruct(T, options, node), .@"union" => return self.parseUnion(T, options, node), .optional => return self.parseOptional(T, options, node), - .void => return self.parseVoid(node), else => @compileError(@typeName(T) ++ ": cannot parse this type"), } } -fn parseVoid(self: @This(), node: NodeIndex) error{ ParserOutOfMemory, Type }!void { - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - const tags = self.ast.nodes.items(.tag); - const data = self.ast.nodes.items(.data); - switch (tags[node]) { - .block_two => if (data[node].lhs != 0 or data[node].rhs != 0) { - return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }); - }, - .block => if (data[node].lhs != data[node].rhs) { - return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }); - }, - else => return self.fail(token, .{ .expected_primitive = .{ .type_name = "void" } }), - } -} - -test "std.zon void" { - const gpa = std.testing.allocator; - - const parsed: void = try parseFromSlice(void, gpa, "{}", .{}); - _ = parsed; - - // Freeing void is a noop, but it should compile! - const free: void = try parseFromSlice(void, gpa, "{}", .{}); - defer parseFree(gpa, free); - - // Other type - { - var ast = try std.zig.Ast.parse(gpa, "123", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(void, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected void", formatted); - } -} - -fn parseOptional(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const optional = @typeInfo(T).optional; - - const tags = self.ast.nodes.items(.tag); - if (tags[node] == .identifier) { - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - const bytes = self.ast.tokenSlice(token); - if (std.mem.eql(u8, bytes, "null")) { - return null; - } +fn parseOptional( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { + if (node.get(self.zoir) == .null) { + return null; } - return try self.parseExpr(optional.child, options, node); + return try self.parseExpr(@typeInfo(T).optional.child, options, node); } test "std.zon optional" { @@ -622,23 +587,28 @@ test "std.zon optional" { // Basic usage { - const none = try parseFromSlice(?u32, gpa, "null", .{}); + const none = try parseFromSlice(?u32, gpa, "null", null, .{}); try std.testing.expect(none == null); - const some = try parseFromSlice(?u32, gpa, "1", .{}); + const some = try parseFromSlice(?u32, gpa, "1", null, .{}); try std.testing.expect(some.? == 1); } // Deep free { - const none = try parseFromSlice(?[]const u8, gpa, "null", .{}); + const none = try parseFromSlice(?[]const u8, gpa, "null", null, .{}); try std.testing.expect(none == null); - const some = try parseFromSlice(?[]const u8, gpa, "\"foo\"", .{}); + const some = try parseFromSlice(?[]const u8, gpa, "\"foo\"", null, .{}); defer parseFree(gpa, some); try std.testing.expectEqualStrings("foo", some.?); } } -fn parseUnion(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { +fn parseUnion( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; @@ -656,59 +626,60 @@ fn parseUnion(self: *@This(), comptime T: type, comptime options: ParseOptions, }; // Parse the union - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - const tags = self.ast.nodes.items(.tag); - if (tags[node] == .enum_literal) { - // The union must be tagged for an enum literal to coerce to it - if (@"union".tag_type == null) { - return self.fail(main_tokens[node], .expected_union); - } - - // Get the index of the named field. We don't use `parseEnum` here as - // the order of the enum and the order of the union might not match! - const field_index = b: { - const bytes = try self.parseIdent(T, token); - break :b field_indices.get(bytes) orelse - return self.failUnexpectedField(T, token); - }; - - // 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.fail(token, .expected_union); + switch (node.get(self.zoir)) { + .enum_literal => |string| { + // The union must be tagged for an enum literal to coerce to it + if (@"union".tag_type == null) { + return self.failNode(node, "expected union"); + } - // Instantiate the union - return @unionInit(T, field_infos[i].name, {}); - }, - else => unreachable, // Can't be out of bounds - } - } else { - var buf: [2]NodeIndex = undefined; - const field_nodes = try self.fields(T, &buf, node); + // Get the index of the named field. We don't use `parseEnum` here as + // the order of the enum and the order of the union might not match! + const field_index = b: { + break :b field_indices.get(string.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, null); + }; - if (field_nodes.len != 1) { - return self.fail(token, .expected_union); - } + // 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"); - // Fill in the field we found - const field_node = field_nodes[0]; - const field_token = self.ast.firstToken(field_node) - 2; - const field_index = b: { - const name = try self.parseIdent(T, field_token); - break :b field_indices.get(name) orelse - return self.failUnexpectedField(T, field_token); - }; + // Instantiate the union + return @unionInit(T, field_infos[i].name, {}); + }, + else => unreachable, // Can't be out of bounds + } + }, + .struct_literal => |struct_fields| { + if (struct_fields.names.len != 1) { + return self.failNode(node, "expected union"); + } - switch (field_index) { - inline 0...field_infos.len - 1 => |i| { - const value = try self.parseExpr(field_infos[i].type, options, field_node); - return @unionInit(T, field_infos[i].name, value); - }, - else => unreachable, // Can't be out of bounds - } + // Fill in the field we found + const field_name = struct_fields.names[0]; + const field_val = struct_fields.vals.at(0); + const field_index = field_indices.get(field_name.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, 0); + + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + if (field_infos[i].type == void) { + return self.failNode( + field_val, + "void union field not expressed as enum literal", + ); + } else { + const value = try self.parseExpr(field_infos[i].type, options, field_val); + return @unionInit(T, field_infos[i].name, value); + } + }, + else => unreachable, // Can't be out of bounds + } + }, + else => return self.failNode(node, "expected union"), } } @@ -720,22 +691,18 @@ test "std.zon 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 parseFromSlice(Tagged, gpa, ".{.x = 1.5}", .{}); + const tagged_x = try parseFromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{}); try std.testing.expectEqual(Tagged{ .x = 1.5 }, tagged_x); - const tagged_y = try parseFromSlice(Tagged, gpa, ".{.@\"y y\" = true}", .{}); + const tagged_y = try parseFromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{}); try std.testing.expectEqual(Tagged{ .@"y y" = true }, tagged_y); - const tagged_z_shorthand = try parseFromSlice(Tagged, gpa, ".z", .{}); + const tagged_z_shorthand = try parseFromSlice(Tagged, gpa, ".z", null, .{}); try std.testing.expectEqual(@as(Tagged, .z), tagged_z_shorthand); - const tagged_zz_shorthand = try parseFromSlice(Tagged, gpa, ".@\"z z\"", .{}); + const tagged_zz_shorthand = try parseFromSlice(Tagged, gpa, ".@\"z z\"", null, .{}); try std.testing.expectEqual(@as(Tagged, .@"z z"), tagged_zz_shorthand); - const tagged_z_explicit = try parseFromSlice(Tagged, gpa, ".{.z = {}}", .{}); - try std.testing.expectEqual(Tagged{ .z = {} }, tagged_z_explicit); - const tagged_zz_explicit = try parseFromSlice(Tagged, gpa, ".{.@\"z z\" = {}}", .{}); - try std.testing.expectEqual(Tagged{ .@"z z" = {} }, tagged_zz_explicit); - const untagged_x = try parseFromSlice(Untagged, gpa, ".{.x = 1.5}", .{}); + const untagged_x = try parseFromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); try std.testing.expect(untagged_x.x == 1.5); - const untagged_y = try parseFromSlice(Untagged, gpa, ".{.@\"y y\" = true}", .{}); + const untagged_y = try parseFromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); try std.testing.expect(untagged_y.@"y y"); } @@ -743,10 +710,10 @@ test "std.zon unions" { { const Union = union(enum) { bar: []const u8, baz: bool }; - const noalloc = try parseFromSlice(Union, gpa, ".{.baz = false}", .{}); + const noalloc = try parseFromSlice(Union, gpa, ".{.baz = false}", null, .{}); try std.testing.expectEqual(Union{ .baz = false }, noalloc); - const alloc = try parseFromSlice(Union, gpa, ".{.bar = \"qux\"}", .{}); + const alloc = try parseFromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{}); defer parseFree(gpa, alloc); try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); } @@ -754,172 +721,80 @@ test "std.zon unions" { // Unknown field { const Union = union { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.z=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:4: unexpected field, supported fields: x, y", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.z=2.5}", &status, .{})); + try std.testing.expectFmt("1:4: error: unexpected field, supported fields: x, y", "{}", .{status}); } - // Unknown field with name that's too long for parse + // Explicit void field { - const Union = union { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.@\"abc\"=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:4: unexpected field, supported fields: x, y", formatted); + const Union = union(enum) { x: void }; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x=1}", &status, .{})); + try std.testing.expectFmt("1:6: error: void union field not expressed as enum literal", "{}", .{status}); } // Extra field { const Union = union { x: f32, y: bool }; - var ast = try std.zig.Ast.parse(gpa, ".{.x = 1.5, .y = true}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected union", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); } // No fields { const Union = union { x: f32, y: bool }; - var ast = try std.zig.Ast.parse(gpa, ".{}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected union", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); } // Enum literals cannot coerce into untagged unions { const Union = union { x: void }; - var ast = try std.zig.Ast.parse(gpa, ".x", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected union", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); } // Unknown field for enum literal coercion { const Union = union(enum) { x: void }; - var ast = try std.zig.Ast.parse(gpa, ".y", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: x", formatted); - } - - // Unknown field for enum literal coercion that's too long for parse - { - const Union = union(enum) { x: void }; - var ast = try std.zig.Ast.parse(gpa, ".@\"abc\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: x", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".y", &status, .{})); + try std.testing.expectFmt("1:2: error: unexpected field, supported fields: x", "{}", .{status}); } // Non void field for enum literal coercion { const Union = union(enum) { x: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".x", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Union, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected union", formatted); - } - - // Union field with @ - { - const U = union(enum) { x: void }; - const tag = try parseFromSlice(U, gpa, ".@\"x\"", .{}); - try std.testing.expectEqual(@as(U, .x), tag); - const initializer = try parseFromSlice(U, gpa, ".{.@\"x\" = {}}", .{}); - try std.testing.expectEqual(U{ .x = {} }, initializer); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); } } -fn elements( - self: @This(), - comptime T: type, - buf: *[2]NodeIndex, - node: NodeIndex, -) error{Type}![]const NodeIndex { - const main_tokens = self.ast.nodes.items(.main_token); - - // Attempt to parse as an array - if (self.ast.fullArrayInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.failTypeExpr(main_tokens[init.ast.type_expr]); - } - return init.ast.elements; - } - - // Attempt to parse as a struct with no fields - if (self.ast.fullStructInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.failTypeExpr(main_tokens[init.ast.type_expr]); - } - if (init.ast.fields.len == 0) { - return init.ast.fields; - } - } - - return self.failExpectedContainer(T, main_tokens[node]); -} - -fn fields( - self: @This(), +fn parseStruct( + self: *@This(), comptime T: type, - buf: *[2]NodeIndex, - node: NodeIndex, -) error{Type}![]const NodeIndex { - const main_tokens = self.ast.nodes.items(.main_token); - - // Attempt to parse as a struct - if (self.ast.fullStructInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.failTypeExpr(main_tokens[init.ast.type_expr]); - } - return init.ast.fields; - } - - // Attempt to parse as a zero length array - if (self.ast.fullArrayInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.failTypeExpr(main_tokens[init.ast.type_expr]); - } - if (init.ast.elements.len != 0) { - return self.failExpectedContainer(T, main_tokens[node]); - } - return init.ast.elements; - } - - // Fail otherwise - return self.failExpectedContainer(T, main_tokens[node]); -} + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { + const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.zoir)) { + .struct_literal => |nodes| nodes, + .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, + else => return self.failExpectedContainer(T, node), + }; -fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const @"struct" = @typeInfo(T).@"struct"; - const field_infos = @"struct".fields; + const field_infos = @typeInfo(T).@"struct".fields; // Gather info on the fields const field_indices = b: { @@ -931,18 +806,14 @@ fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, }; // Parse the struct - var buf: [2]NodeIndex = undefined; - const field_nodes = try self.fields(T, &buf, node); - var result: T = undefined; var field_found: [field_infos.len]bool = .{false} ** field_infos.len; // If we fail partway through, free all already initialized fields var initialized: usize = 0; errdefer if (options.free_on_error and field_infos.len > 0) { - for (field_nodes[0..initialized]) |initialized_field_node| { - const name_runtime = self.parseIdent(T, self.ast.firstToken(initialized_field_node) - 2) catch unreachable; - switch (field_indices.get(name_runtime) orelse continue) { + 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; parseFree(self.gpa, @field(result, name)); @@ -953,27 +824,32 @@ fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, }; // Fill in the fields we found - for (field_nodes) |field_node| { - const name_token = self.ast.firstToken(field_node) - 2; - const i = b: { - const name = try self.parseIdent(T, name_token); + for (0..fields.names.len) |i| { + const field_index = b: { + const name = fields.names[i].get(self.zoir); break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { continue; } else { - return self.failUnexpectedField(T, name_token); + return self.failUnexpectedField(T, node, i); }; }; // We now know the array is not zero sized (assert this so the code compiles) if (field_found.len == 0) unreachable; - if (field_found[i]) { - return self.failDuplicateField(name_token); + if (field_found[field_index]) { + return self.failDuplicateField(node, i); } - field_found[i] = true; + field_found[field_index] = true; - switch (i) { - inline 0...(field_infos.len - 1) => |j| @field(result, field_infos[j].name) = try self.parseExpr(field_infos[j].type, options, field_node), + switch (field_index) { + inline 0...(field_infos.len - 1) => |j| { + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + options, + fields.vals.at(@intCast(i)), + ); + }, else => unreachable, // Can't be out of bounds } @@ -983,13 +859,12 @@ fn parseStruct(self: *@This(), comptime T: type, comptime options: ParseOptions, // Fill in any missing default fields inline for (field_found, 0..) |found, i| { if (!found) { - const field_info = @"struct".fields[i]; + const field_info = field_infos[i]; if (field_info.default_value) |default| { const typed: *const field_info.type = @ptrCast(@alignCast(default)); @field(result, field_info.name) = typed.*; } else { - const main_tokens = self.ast.nodes.items(.main_token); - return self.failMissingField(field_infos[i].name, main_tokens[node]); + return self.failMissingField(field_infos[i].name, node); } } } @@ -1007,16 +882,16 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; const Vec3 = struct { x: f32, y: f32, z: f32 }; - const zero = try parseFromSlice(Vec0, gpa, ".{}", .{}); + const zero = try parseFromSlice(Vec0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Vec0{}, zero); - const one = try parseFromSlice(Vec1, gpa, ".{.x = 1.2}", .{}); + const one = try parseFromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); - const two = try parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", .{}); + const two = try parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 3.4 }, two); - const three = try parseFromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", .{}); + const three = try parseFromSlice(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); } @@ -1024,7 +899,7 @@ test "std.zon structs" { { const Foo = struct { bar: []const u8, baz: []const []const u8 }; - const parsed = try parseFromSlice(Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", .{}); + const parsed = try parseFromSlice(Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); } @@ -1032,43 +907,25 @@ test "std.zon structs" { // Unknown field { const Vec2 = struct { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .z=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:12: unexpected field, supported fields: x, y", formatted); - } - - // Unknown field too long for parse - { - const Vec2 = struct { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .@\"abc\"=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:12: unexpected field, supported fields: x, y", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); + try std.testing.expectFmt("1:12: error: unexpected field, supported fields: x, y", "{}", .{status}); } // Duplicate field { const Vec2 = struct { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .x=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:12: duplicate field", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{})); + try std.testing.expectFmt("1:12: error: duplicate field", "{}", .{status}); } // Ignore unknown fields { const Vec2 = struct { x: f32, y: f32 = 2.0 }; - const parsed = try parseFromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", .{ + const parsed = try parseFromSlice(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); @@ -1077,31 +934,25 @@ test "std.zon structs" { // Unknown field when struct has no fields (regression test) { const Vec2 = struct {}; - var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5, .z=2.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:4: unexpected field, no fields expected", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); + try std.testing.expectFmt("1:4: error: unexpected field, no fields expected", "{}", .{status}); } // Missing field { const Vec2 = struct { x: f32, y: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.x=1.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Vec2, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: missing required field y", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{})); + try std.testing.expectFmt("1:2: error: missing required field y", "{}", .{status}); } // Default field { const Vec2 = struct { x: f32, y: f32 = 1.5 }; - const parsed = try parseFromSlice(Vec2, gpa, ".{.x = 1.2}", .{}); + const parsed = try parseFromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } @@ -1109,14 +960,14 @@ test "std.zon structs" { // incorrect way that broke for enum values) { const Vec0 = struct { x: enum { x } }; - const parsed = try parseFromSlice(Vec0, gpa, ".{ .x = .x }", .{}); + const parsed = try parseFromSlice(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 parseFromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", .{}); + const parsed = try parseFromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); } @@ -1124,81 +975,95 @@ test "std.zon structs" { { // Structs { - const Empty = struct {}; - - var ast = try std.zig.Ast.parse(gpa, "Empty{}", .zon); - defer ast.deinit(gpa); - - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Empty, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = parseFromSlice(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 ast = try std.zig.Ast.parse(gpa, "[3]u8{1, 2, 3}", .zon); - defer ast.deinit(gpa); - - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = parseFromSlice([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 ast = try std.zig.Ast.parse(gpa, "[]u8{1, 2, 3}", .zon); - defer ast.deinit(gpa); - - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = parseFromSlice([]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 { - const Tuple = struct { i32, i32, i32 }; - var ast = try std.zig.Ast.parse(gpa, "Tuple{1, 2, 3}", .zon); - defer ast.deinit(gpa); + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = parseFromSlice(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}); + } - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: ZON cannot contain type expressions", formatted); + // Nested + { + var status: Status = .{}; + defer status.deinit(gpa); + const parsed = parseFromSlice(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}); } } } -fn parseTuple(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const field_infos = @typeInfo(T).@"struct".fields; +fn parseTuple( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.failExpectedContainer(T, node), + }; var result: T = undefined; - // Parse the struct - var buf: [2]NodeIndex = undefined; - const field_nodes = try self.elements(T, &buf, node); - - if (field_nodes.len != field_infos.len) { - const main_tokens = self.ast.nodes.items(.main_token); - return self.failExpectedContainer(T, main_tokens[node]); + const field_infos = @typeInfo(T).@"struct".fields; + if (nodes.len != field_infos.len) { + return self.failExpectedContainer(T, node); } - inline for (field_infos, field_nodes, 0..) |field_info, field_node, initialized| { + inline for (0..field_infos.len) |i| { // If we fail to parse this field, free all fields before it errdefer if (options.free_on_error) { - inline for (0..field_infos.len) |i| { - if (i >= initialized) break; - parseFree(self.gpa, result[i]); + inline for (0..i) |j| { + if (j >= i) break; + parseFree(self.gpa, result[j]); } }; - result[initialized] = try self.parseExpr(field_info.type, options, field_node); + result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); } return result; @@ -1214,23 +1079,23 @@ test "std.zon tuples" { const Tuple2 = struct { f32, bool }; const Tuple3 = struct { f32, bool, u8 }; - const zero = try parseFromSlice(Tuple0, gpa, ".{}", .{}); + const zero = try parseFromSlice(Tuple0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Tuple0{}, zero); - const one = try parseFromSlice(Tuple1, gpa, ".{1.2}", .{}); + const one = try parseFromSlice(Tuple1, gpa, ".{1.2}", null, .{}); try std.testing.expectEqual(Tuple1{1.2}, one); - const two = try parseFromSlice(Tuple2, gpa, ".{1.2, true}", .{}); + const two = try parseFromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); try std.testing.expectEqual(Tuple2{ 1.2, true }, two); - const three = try parseFromSlice(Tuple3, gpa, ".{1.2, false, 3}", .{}); + const three = try parseFromSlice(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 parseFromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", .{}); + const parsed = try parseFromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); } @@ -1238,77 +1103,72 @@ test "std.zon tuples" { // Extra field { const Tuple = struct { f32, bool }; - var ast = try std.zig.Ast.parse(gpa, ".{0.5, true, 123}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); } // Extra field { const Tuple = struct { f32, bool }; - var ast = try std.zig.Ast.parse(gpa, ".{0.5}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); } // Tuple with unexpected field names { const Tuple = struct { f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{.foo = 10.0}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Tuple, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 1 field", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 1 field", "{}", .{status}); } // Struct with missing field names { const Struct = struct { foo: f32 }; - var ast = try std.zig.Ast.parse(gpa, ".{10.0}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Struct, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected struct", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, gpa, ".{10.0}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected struct", "{}", .{status}); } } -fn parseArray(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { +fn parseArray( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.failExpectedContainer(T, node), + }; + const array_info = @typeInfo(T).array; - // Parse the array - var array: T = undefined; - var buf: [2]NodeIndex = undefined; - const element_nodes = try self.elements(T, &buf, node); // Check if the size matches - if (element_nodes.len != array_info.len) { - const main_tokens = self.ast.nodes.items(.main_token); - return self.failExpectedContainer(T, main_tokens[node]); + if (nodes.len != array_info.len) { + return self.failExpectedContainer(T, node); } // Parse the elements and return the array - for (&array, element_nodes, 0..) |*element, element_node, initialized| { + var result: T = undefined; + for (0..result.len) |i| { // If we fail to parse this field, free all fields before it errdefer if (options.free_on_error) { - for (array[0..initialized]) |initialized_item| { - parseFree(self.gpa, initialized_item); + for (result[0..i]) |item| { + parseFree(self.gpa, item); } }; - element.* = try self.parseExpr(array_info.child, options, element_node); + result[i] = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); } - return array; + return result; } // Test sizes 0 to 3 since small sizes get parsed differently @@ -1322,49 +1182,49 @@ test "std.zon arrays and slices" { { // Arrays { - const zero = try parseFromSlice([0]u8, gpa, ".{}", .{}); + const zero = try parseFromSlice([0]u8, gpa, ".{}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); - const one = try parseFromSlice([1]u8, gpa, ".{'a'}", .{}); + const one = try parseFromSlice([1]u8, gpa, ".{'a'}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); - const two = try parseFromSlice([2]u8, gpa, ".{'a', 'b'}", .{}); + const two = try parseFromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two); - const two_comma = try parseFromSlice([2]u8, gpa, ".{'a', 'b',}", .{}); + const two_comma = try parseFromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([2]u8, .{ 'a', 'b' }), &two_comma); - const three = try parseFromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", .{}); + const three = try parseFromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); - const sentinel = try parseFromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", .{}); + const sentinel = try parseFromSlice([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 parseFromSlice([]const u8, gpa, ".{}", .{}); + const zero = try parseFromSlice([]const u8, gpa, ".{}", null, .{}); defer parseFree(gpa, zero); try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); - const one = try parseFromSlice([]u8, gpa, ".{'a'}", .{}); + const one = try parseFromSlice([]u8, gpa, ".{'a'}", null, .{}); defer parseFree(gpa, one); try std.testing.expectEqualSlices(u8, &.{'a'}, one); - const two = try parseFromSlice([]const u8, gpa, ".{'a', 'b'}", .{}); + const two = try parseFromSlice([]const u8, gpa, ".{'a', 'b'}", null, .{}); defer parseFree(gpa, two); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two); - const two_comma = try parseFromSlice([]const u8, gpa, ".{'a', 'b',}", .{}); + const two_comma = try parseFromSlice([]const u8, gpa, ".{'a', 'b',}", null, .{}); defer parseFree(gpa, two_comma); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b' }, two_comma); - const three = try parseFromSlice([]u8, gpa, ".{'a', 'b', 'c'}", .{}); + const three = try parseFromSlice([]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); defer parseFree(gpa, three); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, three); - const sentinel = try parseFromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", .{}); + const sentinel = try parseFromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{}); defer parseFree(gpa, sentinel); const expected_sentinel: [:'z']const u8 = &.{ 'a', 'b', 'c' }; try std.testing.expectEqualSlices(u8, expected_sentinel, sentinel); @@ -1375,7 +1235,7 @@ test "std.zon arrays and slices" { { // Arrays { - const parsed = try parseFromSlice([1][]const u8, gpa, ".{\"abc\"}", .{}); + const parsed = try parseFromSlice([1][]const u8, gpa, ".{\"abc\"}", null, .{}); defer parseFree(gpa, parsed); const expected: [1][]const u8 = .{"abc"}; try std.testing.expectEqualDeep(expected, parsed); @@ -1383,7 +1243,7 @@ test "std.zon arrays and slices" { // Slice literals { - const parsed = try parseFromSlice([]const []const u8, gpa, ".{\"abc\"}", .{}); + const parsed = try parseFromSlice([]const []const u8, gpa, ".{\"abc\"}", null, .{}); defer parseFree(gpa, parsed); const expected: []const []const u8 = &.{"abc"}; try std.testing.expectEqualDeep(expected, parsed); @@ -1394,7 +1254,7 @@ test "std.zon arrays and slices" { { // Arrays { - const sentinel = try parseFromSlice([1:2]u8, gpa, ".{1}", .{}); + const sentinel = try parseFromSlice([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]); @@ -1402,7 +1262,7 @@ test "std.zon arrays and slices" { // Slice literals { - const sentinel = try parseFromSlice([:2]align(4) u8, gpa, ".{1}", .{}); + const sentinel = try parseFromSlice([:2]align(4) u8, gpa, ".{1}", null, .{}); defer parseFree(gpa, sentinel); try std.testing.expectEqual(@as(usize, 1), sentinel.len); try std.testing.expectEqual(@as(u8, 1), sentinel[0]); @@ -1412,70 +1272,52 @@ test "std.zon arrays and slices" { // Expect 0 find 3 { - var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([0]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 0 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 0 fields", "{}", .{status}); } // Expect 1 find 2 { - var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([1]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 1 field", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 1 field", "{}", .{status}); } // Expect 2 find 1 { - var ast = try std.zig.Ast.parse(gpa, ".{'a'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([2]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 2 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); } // Expect 3 find 0 { - var ast = try std.zig.Ast.parse(gpa, ".{}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected tuple with 3 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, ".{}", &status, .{})); + try std.testing.expectFmt("1:2: error: expected tuple with 3 fields", "{}", .{status}); } // Wrong inner type { // Array { - var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([3]bool, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: expected bool", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectFmt("1:3: error: expected bool", "{}", .{status}); } // Slice { - var ast = try std.zig.Ast.parse(gpa, ".{'a', 'b', 'c'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]bool, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: expected bool", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectFmt("1:3: error: expected bool", "{}", .{status}); } } @@ -1483,165 +1325,106 @@ test "std.zon arrays and slices" { { // Array { - var ast = try std.zig.Ast.parse(gpa, "'a'", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([3]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple with 3 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, "'a'", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple with 3 fields", "{}", .{status}); } // Slice { - var ast = try std.zig.Ast.parse(gpa, "'a'", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, "'a'", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } } // Address of is not allowed (indirection for slices in ZON is implicit) { - var ast = try std.zig.Ast.parse(gpa, "&.{'a', 'b', 'c'}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([3]bool, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: ZON cannot take the address of a value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectFmt("1:3: error: pointers are not available in ZON", "{}", .{status}); } } -fn parsePointer(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const tags = self.ast.nodes.items(.tag); - return switch (tags[node]) { - .string_literal => try self.parseStringLiteral(T, node), - .multiline_string_literal => try self.parseMultilineStringLiteral(T, node), - else => self.parseSlice(T, options, node), - }; -} - -fn parseSlice(self: *@This(), comptime T: type, comptime options: ParseOptions, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const pointer = @typeInfo(T).pointer; - // Make sure we're working with a slice - switch (pointer.size) { - .Slice => {}, - .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), - } - - // Parse the array literal - const main_tokens = self.ast.nodes.items(.main_token); - var buf: [2]NodeIndex = undefined; - const element_nodes = try self.elements(T, &buf, node); - - // Allocate the slice - const sentinel = if (pointer.sentinel) |s| @as(*const pointer.child, @ptrCast(s)).* else null; - const slice = self.gpa.allocWithOptions( - pointer.child, - element_nodes.len, - pointer.alignment, - sentinel, - ) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), - }; - errdefer self.gpa.free(slice); - - // Parse the elements and return the slice - for (slice, element_nodes, 0..) |*element, element_node, initialized| { - errdefer if (options.free_on_error) { - for (0..initialized) |i| { - parseFree(self.gpa, slice[i]); - } - }; - element.* = try self.parseExpr(pointer.child, options, element_node); +fn parsePointer( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon }!T { + switch (node.get(self.zoir)) { + .string_literal => |str| return try self.parseString(T, node, str), + .array_literal => |nodes| return try self.parseSlice(T, options, nodes), + .empty_literal => return try self.parseSlice(T, options, .{ .start = node, .len = 0 }), + else => return self.failExpectedContainer(T, node), } - return slice; } -fn parseStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { +fn parseString( + self: *@This(), + comptime T: type, + node: Zoir.Node.Index, + str: []const u8, +) !T { const pointer = @typeInfo(T).pointer; - if (pointer.size != .Slice) { - @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); - } - - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - const raw = self.ast.tokenSlice(token); - - if (pointer.child != u8 or !pointer.is_const or pointer.alignment != 1) { - return self.failExpectedContainer(T, token); - } - var buf = std.ArrayListUnmanaged(u8){}; - defer buf.deinit(self.gpa); - const parse_write_result = std.zig.string_literal.parseWrite( - buf.writer(self.gpa), - raw, - ) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(token), - }; - switch (parse_write_result) { - .success => {}, - .failure => |reason| return self.failInvalidStringLiteral(token, reason), + if (pointer.child != u8 or + pointer.size != .Slice or + !pointer.is_const or + (pointer.sentinel != null and @as(*const u8, @ptrCast(pointer.sentinel)).* != 0) or + pointer.alignment != 1) + { + return self.failExpectedContainer(T, node); } if (pointer.sentinel) |sentinel| { if (@as(*const u8, @ptrCast(sentinel)).* != 0) { - return self.failExpectedContainer(T, token); + return self.failExpectedContainer(T, node); } - return buf.toOwnedSliceSentinel(self.gpa, 0) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(token), - }; + return try self.gpa.dupeZ(u8, str); } - return buf.toOwnedSlice(self.gpa) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(token), - }; + return self.gpa.dupe(pointer.child, str); } -fn parseMultilineStringLiteral(self: *@This(), comptime T: type, node: NodeIndex) error{ ParserOutOfMemory, Type }!T { - const main_tokens = self.ast.nodes.items(.main_token); - +fn parseSlice( + self: *@This(), + comptime T: type, + comptime options: ParseOptions, + nodes: Zoir.Node.Index.Range, +) error{ OutOfMemory, ParseZon }!T { const pointer = @typeInfo(T).pointer; - if (pointer.size != .Slice) { - @compileError(@typeName(T) ++ ": cannot parse pointers that are not slices"); - } - - if (pointer.child != u8 or !pointer.is_const or pointer.alignment != 1) { - return self.failExpectedContainer(T, main_tokens[node]); + // Make sure we're working with a slice + switch (pointer.size) { + .Slice => {}, + .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), } - var buf = std.ArrayListUnmanaged(u8){}; - defer buf.deinit(self.gpa); - const writer = buf.writer(self.gpa); + // Allocate the slice + const sentinel = if (pointer.sentinel) |s| @as(*const pointer.child, @ptrCast(s)).* else null; + const slice = try self.gpa.allocWithOptions( + pointer.child, + nodes.len, + pointer.alignment, + sentinel, + ); + errdefer self.gpa.free(slice); - var parser = std.zig.string_literal.multilineParser(writer); - const data = self.ast.nodes.items(.data); - var tok_i = data[node].lhs; - while (tok_i <= data[node].rhs) : (tok_i += 1) { - const token_slice = self.ast.tokenSlice(tok_i); - parser.line(token_slice) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(tok_i), + // Parse the elements and return the slice + for (0..nodes.len) |i| { + errdefer if (options.free_on_error) { + for (slice[0..i]) |item| { + parseFree(self.gpa, item); + } }; + slice[i] = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); } - if (pointer.sentinel) |sentinel| { - if (@as(*const u8, @ptrCast(sentinel)).* != 0) { - return self.failExpectedContainer(T, main_tokens[node]); - } - return buf.toOwnedSliceSentinel(self.gpa, 0) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), - }; - } else { - return buf.toOwnedSlice(self.gpa) catch |err| switch (err) { - error.OutOfMemory => return self.failOutOfMemory(main_tokens[node]), - }; - } + return slice; } test "std.zon string literal" { @@ -1649,21 +1432,21 @@ test "std.zon string literal" { // Basic string literal { - const parsed = try parseFromSlice([]const u8, gpa, "\"abc\"", .{}); + const parsed = try parseFromSlice([]const u8, gpa, "\"abc\"", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "abc"), parsed); } // String literal with escape characters { - const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\nc\"", .{}); + const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\nc\"", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "ab\nc"), parsed); } // String literal with embedded null { - const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\x00c\"", .{}); + const parsed = try parseFromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); } @@ -1671,23 +1454,23 @@ test "std.zon string literal" { // Passing string literal to a mutable slice { { - var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]u8, gpa, "\"abcd\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "\\\\abcd", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]u8, gpa, "\\\\abcd", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } } @@ -1696,35 +1479,39 @@ test "std.zon string literal" { { var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([4:0]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple with 4 fields", formatted); + var zoir = try ZonGen.generate(gpa, ast); + defer zoir.deinit(gpa); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple with 4 fields", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "\\\\abcd", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([4:0]u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple with 4 fields", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple with 4 fields", "{}", .{status}); } } // Zero termianted slices { { - const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\"abc\"", .{}); + const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\"abc\"", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } { - const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\\\\abc", .{}); + const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\\\\abc", null, .{}); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); @@ -1734,102 +1521,90 @@ test "std.zon string literal" { // Other value terminated slices { { - var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([:1]const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "\\\\foo", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([:1]const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } } // Expecting string literal, getting something else { - var ast = try std.zig.Ast.parse(gpa, "true", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected string", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected string", "{}", .{status}); } // Expecting string literal, getting an incompatible tuple { - var ast = try std.zig.Ast.parse(gpa, ".{false}", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: expected u8", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]const u8, gpa, ".{false}", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: expected u8", "{}", .{status}); } // Invalid string literal { - var ast = try std.zig.Ast.parse(gpa, "\"\\a\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: invalid escape character: 'a'", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), + ); + try std.testing.expectFmt("1:3: error: invalid escape character: 'a'", "{}", .{status}); } // Slice wrong child type { { - var ast = try std.zig.Ast.parse(gpa, "\"a\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]const i8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\"a\"", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "\\\\a", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]const i8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\\\\a", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } } // Bad alignment { { - var ast = try std.zig.Ast.parse(gpa, "\"abc\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]align(2) const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "\\\\abc", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst([]align(2) const u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected tuple", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{})); + try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); } } @@ -1855,11 +1630,11 @@ test "std.zon string literal" { \\ .message2 = \\ \\this too...sort of. \\ , - \\ .message3 = + \\ .message3 = \\ \\ \\ \\and this. \\} - , .{}); + , null, .{}); defer parseFree(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); @@ -1868,13 +1643,9 @@ test "std.zon string literal" { } } -fn parseEnumLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { - const tags = self.ast.nodes.items(.tag); - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - - switch (tags[node]) { - .enum_literal => { +fn parseEnumLiteral(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon}!T { + switch (node.get(self.zoir)) { + .enum_literal => |string| { // Create a comptime string map for the enum fields const enum_fields = @typeInfo(T).@"enum".fields; comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; @@ -1884,44 +1655,13 @@ fn parseEnumLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); // Get the tag if it exists - const bytes = try self.parseIdent(T, token); - return enum_tags.get(bytes) orelse - self.failUnexpectedField(T, token); + return enum_tags.get(string.get(self.zoir)) orelse + self.failUnexpectedField(T, node, null); }, - else => return self.fail(token, .expected_enum), + else => return self.failNode(node, "expected enum literal"), } } -// Note that `parseIdent` may reuse the same buffer when called repeatedly, invalidating -// previous results. -// The resulting bytes may reference a buffer on `self` that can be reused in future calls to -// `parseIdent`. They should only be held onto temporarily. -fn parseIdent(self: @This(), T: type, token: TokenIndex) error{Type}![]const u8 { - var unparsed = self.ast.tokenSlice(token); - - if (unparsed[0] == '@' and unparsed[1] == '"') { - var fba = std.heap.FixedBufferAllocator.init(self.ident_buf); - const alloc = fba.allocator(); - var parsed = std.ArrayListUnmanaged(u8).initCapacity(alloc, self.ident_buf.len) catch unreachable; - - const raw = unparsed[1..unparsed.len]; - const result = std.zig.string_literal.parseWrite(parsed.writer(alloc), raw) catch |err| switch (err) { - // If it's too long for our preallocated buffer, it must be incorrect - error.OutOfMemory => return self.failUnexpectedField(T, token), - }; - switch (result) { - .failure => |reason| return self.failInvalidStringLiteral(token, reason), - .success => {}, - } - if (std.mem.indexOfScalar(u8, parsed.items, 0) != null) { - return self.failUnexpectedField(T, token); - } - return parsed.items; - } - - return unparsed; -} - test "std.zon enum literals" { const gpa = std.testing.allocator; @@ -1933,126 +1673,127 @@ test "std.zon enum literals" { }; // Tags that exist - try std.testing.expectEqual(Enum.foo, try parseFromSlice(Enum, gpa, ".foo", .{})); - try std.testing.expectEqual(Enum.bar, try parseFromSlice(Enum, gpa, ".bar", .{})); - try std.testing.expectEqual(Enum.baz, try parseFromSlice(Enum, gpa, ".baz", .{})); - try std.testing.expectEqual(Enum.@"ab\nc", try parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", .{})); + try std.testing.expectEqual(Enum.foo, try parseFromSlice(Enum, gpa, ".foo", null, .{})); + try std.testing.expectEqual(Enum.bar, try parseFromSlice(Enum, gpa, ".bar", null, .{})); + try std.testing.expectEqual(Enum.baz, try parseFromSlice(Enum, gpa, ".baz", null, .{})); + try std.testing.expectEqual(Enum.@"ab\nc", try parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{})); // Bad tag { - var ast = try std.zig.Ast.parse(gpa, ".qux", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".qux", &status, .{})); + try std.testing.expectFmt( + "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", + "{}", + .{status}, + ); } // Bad tag that's too long for parser { - var ast = try std.zig.Ast.parse(gpa, ".@\"foobarbaz\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{})); + try std.testing.expectFmt( + "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", + "{}", + .{status}, + ); } // Bad type { - var ast = try std.zig.Ast.parse(gpa, "true", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(Enum, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected enum literal", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, "true", &status, .{})); + try std.testing.expectFmt("1:1: error: expected enum literal", "{}", .{status}); } // Test embedded nulls in an identifier { - var ast = try std.zig.Ast.parse(gpa, ".@\"\\x00\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(enum { a }, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: unexpected field, supported fields: a", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), + ); + try std.testing.expectFmt("1:2: error: identifier cannot contain null bytes", "{}", .{status}); } } -fn fail(self: @This(), token: TokenIndex, reason: ParseFailure.Reason) error{Type} { +fn failToken(self: @This(), token: Ast.TokenIndex, message: []const u8) error{ParseZon} { @branchHint(.cold); - if (self.status) |s| s.* = .{ .failure = .{ - .ast = self.ast, + if (self.status) |s| s.type_check = .{ .token = token, - .reason = reason, - } }; - return error.Type; -} - -fn failOutOfMemory(self: *@This(), token: TokenIndex) error{ParserOutOfMemory} { - // Set our failure state, but ignore the type error because users may want to handle out of - // memory separately from other input errors - self.fail(token, .out_of_memory) catch {}; - - // We don't return error.OutOfMemory directly so that we can't forget to call this function, - // this error will be converted to error.OutOfMemory before returning to the user - return error.ParserOutOfMemory; -} - -fn failInvalidStringLiteral(self: @This(), token: TokenIndex, err: StringLiteralError) error{Type} { - @branchHint(.cold); - return self.fail(token, .{ - .invalid_string_literal = .{ .err = err }, - }); -} - -fn failInvalidNumberLiteral(self: @This(), token: TokenIndex, err: NumberLiteralError) error{Type} { - @branchHint(.cold); - return self.fail(token, .{ - .invalid_number_literal = .{ .err = err }, - }); + .message = message, + }; + return error.ParseZon; } -fn failCannotRepresent(self: @This(), comptime T: type, token: TokenIndex) error{Type} { +fn failNode(self: @This(), node: Zoir.Node.Index, message: []const u8) error{ParseZon} { @branchHint(.cold); - return self.fail(token, .{ - .cannot_represent = .{ .type_name = @typeName(T) }, - }); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failToken(token, message); } -fn failNegativeIntegerZero(self: @This(), token: TokenIndex) error{Type} { +fn failCannotRepresent(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon} { @branchHint(.cold); - return self.fail(token, .negative_integer_zero); + return self.failNode(node, @typeName(T) ++ " cannot represent value"); } -fn failUnexpectedField(self: @This(), T: type, token: TokenIndex) error{Type} { +fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?usize) error{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)) { - .@"struct", .@"union", .@"enum" => return self.fail(token, .{ .unexpected_field = .{ - .fields = std.meta.fieldNames(T), - } }), + inline .@"struct", .@"union", .@"enum" => |info| { + if (info.fields.len == 0) { + return self.failToken(token, "unexpected field, no fields expected"); + } else { + comptime var message: []const u8 = "unexpected field, supported fields: "; + inline for (info.fields, 0..) |field_info, i| { + if (i != 0) message = message ++ ", "; + const id_formatter = comptime std.zig.fmtId(field_info.name); + message = message ++ std.fmt.comptimePrint("{}", .{id_formatter}); + } + return self.failToken(token, message); + } + }, else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), } } -fn failExpectedContainer(self: @This(), T: type, token: TokenIndex) error{Type} { +fn failExpectedTupleWithField( + self: @This(), + node: Zoir.Node.Index, + comptime fields: usize, +) error{ParseZon} { + const plural = if (fields == 1) "" else "s"; + return self.failNode( + node, + std.fmt.comptimePrint("expected tuple with {} field{s}", .{ fields, plural }), + ); +} + +fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ParseZon} { @branchHint(.cold); switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { - return self.fail(token, .{ .expected_tuple_with_fields = .{ - .fields = @"struct".fields.len, - } }); + return self.failExpectedTupleWithField(node, @"struct".fields.len); } else { - return self.fail(token, .expected_struct); + return self.failNode(node, "expected struct"); }, - .@"union" => return self.fail(token, .expected_union), - .array => |array| return self.fail(token, .{ .expected_tuple_with_fields = .{ - .fields = array.len, - } }), + .@"union" => return self.failNode(node, "expected union"), + .array => |array| return self.failExpectedTupleWithField(node, array.len), .pointer => |pointer| { if (pointer.child == u8 and pointer.size == .Slice and @@ -2060,240 +1801,114 @@ fn failExpectedContainer(self: @This(), T: type, token: TokenIndex) error{Type} (pointer.sentinel == null or @as(*const u8, @ptrCast(pointer.sentinel)).* == 0) and pointer.alignment == 1) { - return self.fail(token, .expected_string); + return self.failNode(node, "expected string"); } else { - return self.fail(token, .expected_tuple); + return self.failNode(node, "expected tuple"); } }, else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), } } -fn failMissingField(self: @This(), name: []const u8, token: TokenIndex) error{Type} { +fn failMissingField(self: @This(), comptime name: []const u8, node: Zoir.Node.Index) error{ParseZon} { @branchHint(.cold); - return self.fail(token, .{ .missing_field = .{ .field_name = name } }); + return self.failNode(node, "missing required field " ++ name); } -fn failDuplicateField(self: @This(), token: TokenIndex) error{Type} { +fn failDuplicateField(self: @This(), node: Zoir.Node.Index, field: usize) error{ParseZon} { @branchHint(.cold); - return self.fail(token, .duplicate_field); + 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[field]; + const token = self.ast.firstToken(field_node) - 2; + return self.failToken(token, "duplicate field"); } -fn failTypeExpr(self: @This(), token: TokenIndex) error{Type} { - @branchHint(.cold); - return self.fail(token, .type_expr); -} - -fn parseBool(self: @This(), node: NodeIndex) error{Type}!bool { - const tags = self.ast.nodes.items(.tag); - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node]; - switch (tags[node]) { - .identifier => { - const bytes = self.ast.tokenSlice(token); - const map = std.StaticStringMap(bool).initComptime(.{ - .{ "true", true }, - .{ "false", false }, - }); - if (map.get(bytes)) |value| { - return value; - } - }, - else => {}, +fn parseBool(self: @This(), node: Zoir.Node.Index) error{ParseZon}!bool { + switch (node.get(self.zoir)) { + .true => return true, + .false => return false, + else => return self.failNode(node, "expected bool"), } - return self.fail(token, .{ .expected_primitive = .{ .type_name = "bool" } }); } test "std.zon parse bool" { const gpa = std.testing.allocator; // Correct floats - try std.testing.expectEqual(true, try parseFromSlice(bool, gpa, "true", .{})); - try std.testing.expectEqual(false, try parseFromSlice(bool, gpa, "false", .{})); + try std.testing.expectEqual(true, try parseFromSlice(bool, gpa, "true", null, .{})); + try std.testing.expectEqual(false, try parseFromSlice(bool, gpa, "false", null, .{})); // Errors { - var ast = try std.zig.Ast.parse(gpa, " foo", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(bool, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:2: expected bool", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(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 ast = try std.zig.Ast.parse(gpa, "123", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(bool, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected bool", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(bool, gpa, "123", &status, .{})); + try std.testing.expectFmt("1:1: error: expected bool", "{}", .{status}); } } -fn parseNumber( +fn parseInt( self: @This(), comptime T: type, - node: NodeIndex, -) error{Type}!T { - const main_tokens = self.ast.nodes.items(.main_token); - const num_lit_node = self.numLitNode(node); - const tags = self.ast.nodes.items(.tag); - switch (tags[num_lit_node]) { - .number_literal => return self.parseNumberLiteral(T, node), - .char_literal => return self.parseCharLiteral(T, node), - .identifier => switch (@typeInfo(T)) { - .float => { - const token = main_tokens[num_lit_node]; - const bytes = self.ast.tokenSlice(token); - const Ident = enum { inf, nan }; - const map = std.StaticStringMap(Ident).initComptime(.{ - .{ "inf", .inf }, - .{ "nan", .nan }, - }); - if (map.get(bytes)) |value| { - switch (value) { - .inf => if (self.isNegative(node)) { - return -std.math.inf(T); - } else { - return std.math.inf(T); - }, - .nan => return std.math.nan(T), - } - } - }, - else => {}, + node: Zoir.Node.Index, +) error{ParseZon}!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.to(T) catch + self.failCannotRepresent(T, node), }, - else => {}, - } - return self.fail(main_tokens[node], .{ - .expected_primitive = .{ .type_name = @typeName(T) }, - }); -} - -fn parseNumberLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { - const num_lit_node = self.numLitNode(node); - const main_tokens = self.ast.nodes.items(.main_token); - const num_lit_token = main_tokens[num_lit_node]; - const token_bytes = self.ast.tokenSlice(num_lit_token); - const number = std.zig.number_literal.parseNumberLiteral(token_bytes); - - switch (number) { - .int => |int| return self.applySignToInt(T, node, int), - .big_int => |base| return self.parseBigNumber(T, node, base), - .float => return self.parseFloat(T, node), - .failure => |reason| return self.failInvalidNumberLiteral(main_tokens[node], reason), - } -} - -fn applySignToInt(self: @This(), comptime T: type, node: NodeIndex, int: anytype) error{Type}!T { - const main_tokens = self.ast.nodes.items(.main_token); - if (self.isNegative(node)) { - if (int == 0) { - return self.failNegativeIntegerZero(main_tokens[node]); - } - switch (@typeInfo(T)) { - .int => |int_info| switch (int_info.signedness) { - .signed => { - const In = @TypeOf(int); - if (std.math.maxInt(In) > std.math.maxInt(T) and int == @as(In, std.math.maxInt(T)) + 1) { - return std.math.minInt(T); - } + .float_literal => |val| return intFromFloatExact(T, val) orelse + self.failCannotRepresent(T, node), - return -(std.math.cast(T, int) orelse return self.failCannotRepresent(T, main_tokens[node])); - }, - .unsigned => return self.failCannotRepresent(T, main_tokens[node]), - }, - .float => return -@as(T, @floatFromInt(int)), - else => @compileError("internal error: expected numeric type"), - } - } else { - switch (@typeInfo(T)) { - .int => return std.math.cast(T, int) orelse - self.failCannotRepresent(T, main_tokens[node]), - .float => return @as(T, @floatFromInt(int)), - else => @compileError("internal error: expected numeric type"), - } + .char_literal => |val| return std.math.cast(T, val) orelse + self.failCannotRepresent(T, node), + else => return self.failNode(node, "expected " ++ @typeName(T)), } } -fn parseBigNumber( +fn parseFloat( self: @This(), comptime T: type, - node: NodeIndex, - base: Base, -) error{Type}!T { - switch (@typeInfo(T)) { - .int => return self.parseBigInt(T, node, base), - .float => { - const result = @as(T, @floatCast(try self.parseFloat(f128, node))); - if (std.math.isNegativeZero(result)) { + node: Zoir.Node.Index, +) error{ParseZon}!T { + switch (node.get(self.zoir)) { + .int_literal => |int| switch (int) { + .small => |val| return @floatFromInt(val), + .big => { const main_tokens = self.ast.nodes.items(.main_token); - return self.failNegativeIntegerZero(main_tokens[node]); - } - return result; + const tags = self.ast.nodes.items(.tag); + const data = self.ast.nodes.items(.data); + const ast_node = node.getAstNode(self.zoir); + const negative = tags[ast_node] == .negation; + const num_lit_node = if (negative) data[ast_node].lhs else ast_node; + const token = main_tokens[num_lit_node]; + const bytes = self.ast.tokenSlice(token); + const unsigned = std.fmt.parseFloat(T, bytes) catch { + // Bytes already validated by big int parser + unreachable; + }; + return if (negative) -unsigned else unsigned; + }, }, - else => @compileError("internal error: expected integer or float type"), - } -} - -fn parseBigInt(self: @This(), comptime T: type, node: NodeIndex, base: Base) error{Type}!T { - const num_lit_node = self.numLitNode(node); - const main_tokens = self.ast.nodes.items(.main_token); - const num_lit_token = main_tokens[num_lit_node]; - const prefix_offset: usize = if (base == .decimal) 0 else 2; - const bytes = self.ast.tokenSlice(num_lit_token)[prefix_offset..]; - const result = if (self.isNegative(node)) - std.fmt.parseIntWithSign(T, u8, bytes, @intFromEnum(base), .neg) - else - std.fmt.parseIntWithSign(T, u8, bytes, @intFromEnum(base), .pos); - return result catch |err| switch (err) { - error.InvalidCharacter => unreachable, - error.Overflow => return self.failCannotRepresent(T, main_tokens[node]), - }; -} - -fn parseFloat( - self: @This(), - comptime T: type, - node: NodeIndex, -) error{Type}!T { - const num_lit_node = self.numLitNode(node); - const main_tokens = self.ast.nodes.items(.main_token); - const num_lit_token = main_tokens[num_lit_node]; - const bytes = self.ast.tokenSlice(num_lit_token); - const Float = if (@typeInfo(T) == .float) T else f128; - const unsigned_float = std.fmt.parseFloat(Float, bytes) catch unreachable; // Already validated - const result = if (self.isNegative(node)) -unsigned_float else unsigned_float; - switch (@typeInfo(T)) { - .float => return @as(T, @floatCast(result)), - .int => return intFromFloatExact(T, result) orelse - return self.failCannotRepresent(T, main_tokens[node]), - else => @compileError("internal error: expected integer or float type"), - } -} - -fn parseCharLiteral(self: @This(), comptime T: type, node: NodeIndex) error{Type}!T { - const num_lit_node = self.numLitNode(node); - const main_tokens = self.ast.nodes.items(.main_token); - const num_lit_token = main_tokens[num_lit_node]; - const token_bytes = self.ast.tokenSlice(num_lit_token); - const char = std.zig.string_literal.parseCharLiteral(token_bytes).success; - return self.applySignToInt(T, node, char); -} - -fn isNegative(self: *const @This(), node: NodeIndex) bool { - const tags = self.ast.nodes.items(.tag); - return tags[node] == .negation; -} - -fn numLitNode(self: *const @This(), node: NodeIndex) NodeIndex { - if (self.isNegative(node)) { - const data = self.ast.nodes.items(.data); - return data[node].lhs; - } else { - return node; + .float_literal => |val| return @floatCast(val), + .pos_inf => return std.math.inf(T), + .neg_inf => return -std.math.inf(T), + .nan => return std.math.nan(T), + .char_literal => |val| return @floatFromInt(val), + else => return self.failNode(node, "expected " ++ @typeName(T)), } } @@ -2343,271 +1958,261 @@ test "std.zon parse int" { const gpa = std.testing.allocator; // Test various numbers and types - try std.testing.expectEqual(@as(u8, 10), try parseFromSlice(u8, gpa, "10", .{})); - try std.testing.expectEqual(@as(i16, 24), try parseFromSlice(i16, gpa, "24", .{})); - try std.testing.expectEqual(@as(i14, -4), try parseFromSlice(i14, gpa, "-4", .{})); - try std.testing.expectEqual(@as(i32, -123), try parseFromSlice(i32, gpa, "-123", .{})); + try std.testing.expectEqual(@as(u8, 10), try parseFromSlice(u8, gpa, "10", null, .{})); + try std.testing.expectEqual(@as(i16, 24), try parseFromSlice(i16, gpa, "24", null, .{})); + try std.testing.expectEqual(@as(i14, -4), try parseFromSlice(i14, gpa, "-4", null, .{})); + try std.testing.expectEqual(@as(i32, -123), try parseFromSlice(i32, gpa, "-123", null, .{})); // Test limits - try std.testing.expectEqual(@as(i8, 127), try parseFromSlice(i8, gpa, "127", .{})); - try std.testing.expectEqual(@as(i8, -128), try parseFromSlice(i8, gpa, "-128", .{})); + try std.testing.expectEqual(@as(i8, 127), try parseFromSlice(i8, gpa, "127", null, .{})); + try std.testing.expectEqual(@as(i8, -128), try parseFromSlice(i8, gpa, "-128", null, .{})); // Test characters - try std.testing.expectEqual(@as(u8, 'a'), try parseFromSlice(u8, gpa, "'a'", .{})); - try std.testing.expectEqual(@as(u8, 'z'), try parseFromSlice(u8, gpa, "'z'", .{})); - try std.testing.expectEqual(@as(i16, -'a'), try parseFromSlice(i16, gpa, "-'a'", .{})); - try std.testing.expectEqual(@as(i16, -'z'), try parseFromSlice(i16, gpa, "-'z'", .{})); + try std.testing.expectEqual(@as(u8, 'a'), try parseFromSlice(u8, gpa, "'a'", null, .{})); + try std.testing.expectEqual(@as(u8, 'z'), try parseFromSlice(u8, gpa, "'z'", null, .{})); // Test big integers try std.testing.expectEqual( @as(u65, 36893488147419103231), - try parseFromSlice(u65, gpa, "36893488147419103231", .{}), + try parseFromSlice(u65, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(u65, 36893488147419103231), - try parseFromSlice(u65, gpa, "368934_881_474191032_31", .{}), + try parseFromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), ); // Test big integer limits try std.testing.expectEqual( @as(i66, 36893488147419103231), - try parseFromSlice(i66, gpa, "36893488147419103231", .{}), + try parseFromSlice(i66, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(i66, -36893488147419103232), - try parseFromSlice(i66, gpa, "-36893488147419103232", .{}), + try parseFromSlice(i66, gpa, "-36893488147419103232", null, .{}), ); { - var ast = try std.zig.Ast.parse(gpa, "36893488147419103232", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(i66, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: i66 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "36893488147419103232", &status, .{})); + try std.testing.expectFmt("1:1: error: i66 cannot represent value", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "-36893488147419103233", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(i66, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: i66 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "-36893488147419103233", &status, .{})); + try std.testing.expectFmt("1:1: error: i66 cannot represent value", "{}", .{status}); } // Test parsing whole number floats as integers - try std.testing.expectEqual(@as(i8, -1), try parseFromSlice(i8, gpa, "-1.0", .{})); - try std.testing.expectEqual(@as(i8, 123), try parseFromSlice(i8, gpa, "123.0", .{})); + try std.testing.expectEqual(@as(i8, -1), try parseFromSlice(i8, gpa, "-1.0", null, .{})); + try std.testing.expectEqual(@as(i8, 123), try parseFromSlice(i8, gpa, "123.0", null, .{})); // Test non-decimal integers - try std.testing.expectEqual(@as(i16, 0xff), try parseFromSlice(i16, gpa, "0xff", .{})); - try std.testing.expectEqual(@as(i16, -0xff), try parseFromSlice(i16, gpa, "-0xff", .{})); - try std.testing.expectEqual(@as(i16, 0o77), try parseFromSlice(i16, gpa, "0o77", .{})); - try std.testing.expectEqual(@as(i16, -0o77), try parseFromSlice(i16, gpa, "-0o77", .{})); - try std.testing.expectEqual(@as(i16, 0b11), try parseFromSlice(i16, gpa, "0b11", .{})); - try std.testing.expectEqual(@as(i16, -0b11), try parseFromSlice(i16, gpa, "-0b11", .{})); + try std.testing.expectEqual(@as(i16, 0xff), try parseFromSlice(i16, gpa, "0xff", null, .{})); + try std.testing.expectEqual(@as(i16, -0xff), try parseFromSlice(i16, gpa, "-0xff", null, .{})); + try std.testing.expectEqual(@as(i16, 0o77), try parseFromSlice(i16, gpa, "0o77", null, .{})); + try std.testing.expectEqual(@as(i16, -0o77), try parseFromSlice(i16, gpa, "-0o77", null, .{})); + try std.testing.expectEqual(@as(i16, 0b11), try parseFromSlice(i16, gpa, "0b11", null, .{})); + try std.testing.expectEqual(@as(i16, -0b11), try parseFromSlice(i16, gpa, "-0b11", null, .{})); // Test non-decimal big integers try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( u65, gpa, "0x1ffffffffffffffff", + null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "0x1ffffffffffffffff", + null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "-0x1ffffffffffffffff", + null, .{}, )); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( u65, gpa, "0o3777777777777777777777", + null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "0o3777777777777777777777", + null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "-0o3777777777777777777777", + null, .{}, )); try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( u65, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", + null, .{}, )); try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", + null, .{}, )); try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( i66, gpa, "-0b11111111111111111111111111111111111111111111111111111111111111111", + null, .{}, )); // Number with invalid character in the middle { - var ast = try std.zig.Ast.parse(gpa, "32a32", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: invalid digit 'a' for decimal base", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "32a32", &status, .{})); + try std.testing.expectFmt("1:3: error: invalid digit 'a' for decimal base", "{}", .{status}); } // Failing to parse as int { - var ast = try std.zig.Ast.parse(gpa, "true", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected u8", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "true", &status, .{})); + try std.testing.expectFmt("1:1: error: expected u8", "{}", .{status}); } // Failing because an int is out of range { - var ast = try std.zig.Ast.parse(gpa, "256", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "256", &status, .{})); + try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); } // Failing because a negative int is out of range { - var ast = try std.zig.Ast.parse(gpa, "-129", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: i8 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-129", &status, .{})); + try std.testing.expectFmt("1:1: error: i8 cannot represent value", "{}", .{status}); } // Failing because an unsigned int is negative { - var ast = try std.zig.Ast.parse(gpa, "-1", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1", &status, .{})); + try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); } // Failing because a float is non-whole { - var ast = try std.zig.Ast.parse(gpa, "1.5", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "1.5", &status, .{})); + try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); } // Failing because a float is negative { - var ast = try std.zig.Ast.parse(gpa, "-1.0", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: u8 cannot represent value", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1.0", &status, .{})); + try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); } // Negative integer zero { - var ast = try std.zig.Ast.parse(gpa, "-0", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: integer literal '-0' is ambiguous", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(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 ast = try std.zig.Ast.parse(gpa, "-0", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: integer literal '-0' is ambiguous", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(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 parseFromSlice(f32, gpa, "-0.0", .{}))); - try std.testing.expect(std.math.isPositiveZero(try parseFromSlice(f32, gpa, "0.0", .{}))); + try std.testing.expect(std.math.isNegativeZero(try parseFromSlice(f32, gpa, "-0.0", null, .{}))); + try std.testing.expect(std.math.isPositiveZero(try parseFromSlice(f32, gpa, "0.0", null, .{}))); // Double negation is not allowed { - var ast = try std.zig.Ast.parse(gpa, "--2", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(i8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected i8", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "--2", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); } { - var ast = try std.zig.Ast.parse(gpa, "--2.0", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected f32", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "--2.0", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); } // Invalid int literal { - var ast = try std.zig.Ast.parse(gpa, "0xg", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:3: invalid digit 'g' for hex base", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "0xg", &status, .{})); + try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base", "{}", .{status}); } // Notes on invalid int literal { - var ast = try std.zig.Ast.parse(gpa, "0123", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(u8, gpa, &ast, &status, .{})); - try std.testing.expectFmt("1:1: number '0123' has leading zero", "{}", .{status.failure}); - try std.testing.expectEqual(1, status.failure.noteCount()); - try std.testing.expectFmt("use '0o' prefix for octal literals", "{}", .{status.failure.fmtNote(0)}); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(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, parseFromSlice(f32, gpa, "-'a'", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + } + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i16, gpa, "-'a'", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); } } @@ -2615,91 +2220,126 @@ test "std.zon parse float" { const gpa = std.testing.allocator; // Test decimals - try std.testing.expectEqual(@as(f16, 0.5), try parseFromSlice(f16, gpa, "0.5", .{})); - try std.testing.expectEqual(@as(f32, 123.456), try parseFromSlice(f32, gpa, "123.456", .{})); - try std.testing.expectEqual(@as(f64, -123.456), try parseFromSlice(f64, gpa, "-123.456", .{})); - try std.testing.expectEqual(@as(f128, 42.5), try parseFromSlice(f128, gpa, "42.5", .{})); + try std.testing.expectEqual(@as(f16, 0.5), try parseFromSlice(f16, gpa, "0.5", null, .{})); + try std.testing.expectEqual(@as(f32, 123.456), try parseFromSlice(f32, gpa, "123.456", null, .{})); + try std.testing.expectEqual(@as(f64, -123.456), try parseFromSlice(f64, gpa, "-123.456", null, .{})); + try std.testing.expectEqual(@as(f128, 42.5), try parseFromSlice(f128, gpa, "42.5", null, .{})); // Test whole numbers with and without decimals - try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5.0", .{})); - try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5", .{})); - try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102.0", .{})); - try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102", .{})); + try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5.0", null, .{})); + try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5", null, .{})); + try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102.0", null, .{})); + try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102", null, .{})); // Test characters and negated characters - try std.testing.expectEqual(@as(f32, 'a'), try parseFromSlice(f32, gpa, "'a'", .{})); - try std.testing.expectEqual(@as(f32, 'z'), try parseFromSlice(f32, gpa, "'z'", .{})); - try std.testing.expectEqual(@as(f32, -'z'), try parseFromSlice(f32, gpa, "-'z'", .{})); + try std.testing.expectEqual(@as(f32, 'a'), try parseFromSlice(f32, gpa, "'a'", null, .{})); + try std.testing.expectEqual(@as(f32, 'z'), try parseFromSlice(f32, gpa, "'z'", null, .{})); // Test big integers try std.testing.expectEqual( @as(f32, 36893488147419103231), - try parseFromSlice(f32, gpa, "36893488147419103231", .{}), + try parseFromSlice(f32, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(f32, -36893488147419103231), - try parseFromSlice(f32, gpa, "-36893488147419103231", .{}), + try parseFromSlice(f32, gpa, "-36893488147419103231", null, .{}), ); try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try parseFromSlice( f128, gpa, "0x1ffffffffffffffff", + null, .{}, )); try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try parseFromSlice( f32, gpa, "0x1ffffffffffffffff", + null, .{}, )); // Exponents, underscores - try std.testing.expectEqual(@as(f32, 123.0E+77), try parseFromSlice(f32, gpa, "12_3.0E+77", .{})); + try std.testing.expectEqual(@as(f32, 123.0E+77), try parseFromSlice(f32, gpa, "12_3.0E+77", null, .{})); // Hexadecimal - try std.testing.expectEqual(@as(f32, 0x103.70p-5), try parseFromSlice(f32, gpa, "0x103.70p-5", .{})); - try std.testing.expectEqual(@as(f32, -0x103.70), try parseFromSlice(f32, gpa, "-0x103.70", .{})); + try std.testing.expectEqual(@as(f32, 0x103.70p-5), try parseFromSlice(f32, gpa, "0x103.70p-5", null, .{})); + try std.testing.expectEqual(@as(f32, -0x103.70), try parseFromSlice(f32, gpa, "-0x103.70", null, .{})); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), - try parseFromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", .{}), + try parseFromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), ); // inf, nan - try std.testing.expect(std.math.isPositiveInf(try parseFromSlice(f32, gpa, "inf", .{}))); - try std.testing.expect(std.math.isNegativeInf(try parseFromSlice(f32, gpa, "-inf", .{}))); - try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "nan", .{}))); - try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "-nan", .{}))); + try std.testing.expect(std.math.isPositiveInf(try parseFromSlice(f32, gpa, "inf", null, .{}))); + try std.testing.expect(std.math.isNegativeInf(try parseFromSlice(f32, gpa, "-inf", null, .{}))); + try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "nan", null, .{}))); + + // Negative nan not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-nan", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + } + + // nan as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + } + + // nan as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + } + + // inf as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "inf", &status, .{})); + try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + } + + // -inf as int not allowed + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-inf", &status, .{})); + try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + } // Bad identifier as float { - var ast = try std.zig.Ast.parse(gpa, "foo", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected f32", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(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 ast = try std.zig.Ast.parse(gpa, "-foo", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected f32", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-foo", &status, .{})); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); } // Non float as float { - var ast = try std.zig.Ast.parse(gpa, "\"foo\"", .zon); - defer ast.deinit(gpa); - var status: ParseStatus = undefined; - try std.testing.expectError(error.Type, parseFromAst(f32, gpa, &ast, &status, .{})); - const formatted = try std.fmt.allocPrint(gpa, "{}", .{status.failure}); - defer gpa.free(formatted); - try std.testing.expectEqualStrings("1:1: expected f32", formatted); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "\"foo\"", &status, .{})); + try std.testing.expectFmt("1:1: error: expected f32", "{}", .{status}); } } @@ -2711,13 +2351,13 @@ test "std.zon free on error" { y: []const u8, z: bool, }; - try std.testing.expectError(error.Type, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\ .y = "world", \\ .z = "fail", \\} - , .{})); + , null, .{})); } // Test freeing partially allocated tuples @@ -2727,13 +2367,13 @@ test "std.zon free on error" { []const u8, bool, }; - try std.testing.expectError(error.Type, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, \\.{ \\ "hello", \\ "world", \\ "fail", \\} - , .{})); + , null, .{})); } // Test freeing structs with missing fields @@ -2742,46 +2382,52 @@ test "std.zon free on error" { x: []const u8, y: bool, }; - try std.testing.expectError(error.Type, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\} - , .{})); + , null, .{})); } // Test freeing partially allocated arrays { - try std.testing.expectError(error.Type, parseFromSlice([3][]const u8, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice([3][]const u8, std.testing.allocator, \\.{ \\ "hello", \\ false, \\ false, \\} - , .{})); + , null, .{})); } // Test freeing partially allocated slices { - try std.testing.expectError(error.Type, parseFromSlice([][]const u8, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice([][]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 parseFromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", .{})).x, + (try parseFromSlice(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 parseFromSlice(union { x: []const u8 }, std.testing.allocator, ".{ .x = \"foo\" }", .{}); + const result = try parseFromSlice( + union { x: []const u8 }, + std.testing.allocator, + ".{ .x = \"foo\" }", + null, + .{}, + ); defer parseFree(std.testing.allocator, result.x); try std.testing.expectEqualStrings("foo", result.x); } @@ -2794,7 +2440,7 @@ test "std.zon free on error" { union { x: []const u8 }, bool, }; - const result = try parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", .{ + const result = try parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", null, .{ .free_on_error = false, }); defer parseFree(std.testing.allocator, result[0].x); @@ -2808,9 +2454,15 @@ test "std.zon free on error" { a: union { x: []const u8 }, b: bool, }; - const result = try parseFromSlice(S, std.testing.allocator, ".{ .a = .{ .x = \"foo\" }, .b = true }", .{ - .free_on_error = false, - }); + const result = try parseFromSlice( + S, + std.testing.allocator, + ".{ .a = .{ .x = \"foo\" }, .b = true }", + null, + .{ + .free_on_error = false, + }, + ); defer parseFree(std.testing.allocator, result.a.x); try std.testing.expectEqualStrings("foo", result.a.x); try std.testing.expect(result.b); @@ -2819,9 +2471,15 @@ test "std.zon free on error" { // Again but for arrays. { const S = [2]union { x: []const u8 }; - const result = try parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", .{ - .free_on_error = false, - }); + const result = try parseFromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", + null, + .{ + .free_on_error = false, + }, + ); defer parseFree(std.testing.allocator, result[0].x); defer parseFree(std.testing.allocator, result[1].x); try std.testing.expectEqualStrings("foo", result[0].x); @@ -2831,9 +2489,15 @@ test "std.zon free on error" { // Again but for slices. { const S = []union { x: []const u8 }; - const result = try parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", .{ - .free_on_error = false, - }); + const result = try parseFromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", + null, + .{ + .free_on_error = false, + }, + ); defer std.testing.allocator.free(result); defer parseFree(std.testing.allocator, result[0].x); defer parseFree(std.testing.allocator, result[1].x); From 2fe8f5328adb8bc092b8db096327f86f7e48d422 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 20 Dec 2024 21:41:42 -0800 Subject: [PATCH 28/51] Parses to zoir on import, but does not yet use result or lower errors --- src/zon.zig | 49 +++++++++++++++++++++---------- test/behavior/zon.zig | 26 ++++------------ test/behavior/zon/a_neg.zon | 1 - test/behavior/zon/floats.zon | 1 - test/behavior/zon/inf_and_nan.zon | 1 - test/behavior/zon/union4.zon | 1 - test/behavior/zon/void.zon | 1 - 7 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 test/behavior/zon/a_neg.zon delete mode 100644 test/behavior/zon/union4.zon delete mode 100644 test/behavior/zon/void.zon diff --git a/src/zon.zig b/src/zon.zig index 0caa6c22994a..d2bd35ec2079 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -15,6 +15,8 @@ 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 ZonGen = std.zig.ZonGen; +const Zoir = std.zig.Zoir; const LowerZon = @This(); @@ -22,6 +24,7 @@ sema: *Sema, file: *File, file_index: Zcu.File.Index, import_loc: LazySrcLoc, +zoir: Zoir, /// Lowers the given file as ZON. pub fn lower( @@ -31,18 +34,26 @@ pub fn lower( res_ty: Type, import_loc: LazySrcLoc, ) CompileError!InternPool.Index { + const ast = file.getTree(sema.gpa) catch unreachable; // Already validated + if (ast.errors.len != 0) { + return lowerAstErrors(file, sema, file_index); + } + + var zoir = try ZonGen.generate(sema.gpa, ast.*); + defer zoir.deinit(sema.gpa); + if (zoir.hasCompileErrors()) { + return lowerZoirErrors(file, zoir, sema, file_index); + } + const lower_zon: LowerZon = .{ .sema = sema, .file = file, .file_index = file_index, .import_loc = import_loc, + .zoir = zoir, }; - const tree = lower_zon.file.getTree(lower_zon.sema.gpa) catch unreachable; // Already validated - if (tree.errors.len != 0) { - return lower_zon.lowerAstErrors(); - } - const data = tree.nodes.items(.data); + const data = ast.nodes.items(.data); const root = data[0].lhs; return lower_zon.lowerExpr(root, res_ty); } @@ -72,12 +83,12 @@ fn fail( return error.AnalysisFail; } -fn lowerAstErrors(self: LowerZon) CompileError { - const tree = self.file.tree; +fn lowerAstErrors(file: *File, sema: *Sema, file_index: Zcu.File.Index) CompileError { + const tree = file.tree; assert(tree.errors.len > 0); - const gpa = self.sema.gpa; - const ip = &self.sema.pt.zcu.intern_pool; + const gpa = sema.gpa; + const ip = &sema.pt.zcu.intern_pool; const parse_err = tree.errors[0]; var buf: std.ArrayListUnmanaged(u8) = .{}; @@ -90,7 +101,7 @@ fn lowerAstErrors(self: LowerZon) CompileError { gpa, .{ .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = self.file_index, + .file = file_index, .inst = .main_struct_inst, }), .offset = .{ .token_abs = parse_err.token + @intFromBool(parse_err.token_is_prev) }, @@ -105,10 +116,10 @@ fn lowerAstErrors(self: LowerZon) CompileError { if (token_tags[parse_err.token + @intFromBool(parse_err.token_is_prev)] == .invalid) { const bad_off: u32 = @intCast(tree.tokenSlice(parse_err.token + @intFromBool(parse_err.token_is_prev)).len); const byte_abs = token_starts[parse_err.token + @intFromBool(parse_err.token_is_prev)] + bad_off; - try self.sema.pt.zcu.errNote( + try sema.pt.zcu.errNote( .{ .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = self.file_index, + .file = file_index, .inst = .main_struct_inst, }), .offset = .{ .byte_abs = byte_abs }, @@ -125,10 +136,10 @@ fn lowerAstErrors(self: LowerZon) CompileError { buf.clearRetainingCapacity(); try tree.renderError(note, buf.writer(gpa)); - try self.sema.pt.zcu.errNote( + try sema.pt.zcu.errNote( .{ .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = self.file_index, + .file = file_index, .inst = .main_struct_inst, }), .offset = .{ .token_abs = note.token + @intFromBool(note.token_is_prev) }, @@ -139,10 +150,18 @@ fn lowerAstErrors(self: LowerZon) CompileError { ); } - try self.sema.pt.zcu.failed_files.putNoClobber(gpa, self.file, err_msg); + try sema.pt.zcu.failed_files.putNoClobber(gpa, file, err_msg); return error.AnalysisFail; } +fn lowerZoirErrors(file: *File, zoir: Zoir, sema: *Sema, file_index: Zcu.File.Index) CompileError { + _ = file; + _ = zoir; + _ = sema; + _ = file_index; + @panic("unimplemented"); +} + const Ident = struct { bytes: []const u8, owned: bool, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index f0408fb18cfb..2e68496b6d42 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -6,10 +6,6 @@ const expectEqualDeep = std.testing.expectEqualDeep; const expectEqualSlices = std.testing.expectEqualSlices; const expectEqualStrings = std.testing.expectEqualStrings; -test "void" { - try expectEqual({}, @as(void, @import("zon/void.zon"))); -} - test "bool" { try expectEqual(true, @as(bool, @import("zon/true.zon"))); try expectEqual(false, @as(bool, @import("zon/false.zon"))); @@ -36,12 +32,10 @@ test "union" { const union1: Union = @import("zon/union1.zon"); const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); try expectEqual(union3.z, {}); - try expectEqual(union4.z, {}); } // Inferred tag @@ -55,12 +49,10 @@ test "union" { const union1: Union = @import("zon/union1.zon"); const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); try expectEqual(union3.z, {}); - try expectEqual(union4.z, {}); } // Explicit tag @@ -79,12 +71,10 @@ test "union" { const union1: Union = @import("zon/union1.zon"); const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - const union4: Union = @import("zon/union4.zon"); try expectEqual(union1.x, 1.5); try expectEqual(union2.y, true); try expectEqual(union3.z, {}); - try expectEqual(union4.z, {}); } } @@ -125,7 +115,6 @@ test "tuple" { test "char" { try expectEqual(@as(u8, 'a'), @as(u8, @import("zon/a.zon"))); try expectEqual(@as(u8, 'z'), @as(u8, @import("zon/z.zon"))); - try expectEqual(@as(i8, -'a'), @as(i8, @import("zon/a_neg.zon"))); } test "arrays" { @@ -280,7 +269,6 @@ test "floats" { // Test characters and negated characters @as(f32, 'a'), @as(f32, 'z'), - @as(f32, -'z'), // Test big integers @as(f32, 36893488147419103231), @@ -303,19 +291,17 @@ test "floats" { test "inf and nan" { // comptime float { - const actual: struct { comptime_float, comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); + const actual: struct { comptime_float, comptime_float, comptime_float } = @import("zon/inf_and_nan.zon"); try expect(std.math.isNan(actual[0])); - try expect(std.math.isNan(actual[1])); - try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[2])))); - try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[3])))); + try expect(std.math.isPositiveInf(@as(f128, @floatCast(actual[1])))); + try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[2])))); } // f32 { - const actual: struct { f32, f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon"); try expect(std.math.isNan(actual[0])); - try expect(std.math.isNan(actual[1])); - try expect(std.math.isPositiveInf(actual[2])); - try expect(std.math.isNegativeInf(actual[3])); + try expect(std.math.isPositiveInf(actual[1])); + try expect(std.math.isNegativeInf(actual[2])); } } diff --git a/test/behavior/zon/a_neg.zon b/test/behavior/zon/a_neg.zon deleted file mode 100644 index b14b16f3d6e9..000000000000 --- a/test/behavior/zon/a_neg.zon +++ /dev/null @@ -1 +0,0 @@ --'a' diff --git a/test/behavior/zon/floats.zon b/test/behavior/zon/floats.zon index 4ea199087977..052e34898923 100644 --- a/test/behavior/zon/floats.zon +++ b/test/behavior/zon/floats.zon @@ -11,7 +11,6 @@ 'a', 'z', - -'z', 36893488147419103231, -36893488147419103231, diff --git a/test/behavior/zon/inf_and_nan.zon b/test/behavior/zon/inf_and_nan.zon index 0b264f8ded4d..dec18d858a05 100644 --- a/test/behavior/zon/inf_and_nan.zon +++ b/test/behavior/zon/inf_and_nan.zon @@ -1,6 +1,5 @@ .{ nan, - -nan, inf, -inf, } diff --git a/test/behavior/zon/union4.zon b/test/behavior/zon/union4.zon deleted file mode 100644 index 4224d9968bd1..000000000000 --- a/test/behavior/zon/union4.zon +++ /dev/null @@ -1 +0,0 @@ -.{ .z = {} } diff --git a/test/behavior/zon/void.zon b/test/behavior/zon/void.zon deleted file mode 100644 index 9e26dfeeb6e6..000000000000 --- a/test/behavior/zon/void.zon +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file From 15b81801f462efd7a4ffb3eb0c6b4a276c1038c8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 15:04:32 -0800 Subject: [PATCH 29/51] Reports zongen errors when importing zon, doesn't yet use it for parsing --- src/Compilation.zig | 22 ++++- src/Zcu.zig | 13 +++ src/zon.zig | 96 ++----------------- .../compile_errors/@import_zon_addr_slice.zig | 3 +- .../@import_zon_double_negation_float.zig | 3 +- .../@import_zon_double_negation_int.zig | 3 +- .../@import_zon_enum_embedded_null.zig | 1 - .../@import_zon_invalid_character.zig | 1 - .../@import_zon_invalid_string.zig | 1 - .../@import_zon_negative_zero.zig | 5 +- .../compile_errors/@import_zon_type_decl.zig | 3 +- .../@import_zon_type_expr_array.zig | 4 +- .../@import_zon_type_expr_fn.zig | 4 +- .../@import_zon_type_expr_struct.zig | 4 +- .../@import_zon_type_expr_tuple.zig | 4 +- .../@import_zon_unescaped_newline.zig | 1 - .../@import_zon_unknown_ident.zig | 5 +- 17 files changed, 55 insertions(+), 118 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index ddac17139a73..4bc7b1af195f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3097,10 +3097,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { if (error_msg) |msg| { try addModuleErrorMsg(zcu, &bundle, msg.*); } else { - // Must be ZIR errors. Note that this may include AST errors. - // addZirErrorMessages asserts that the tree is loaded. - _ = try file.getTree(gpa); - try addZirErrorMessages(&bundle, file); + // Must be ZIR or Zoir errors. Note that this may include AST errors. + _ = try file.getTree(gpa); // Tree must be loaded. + if (file.zir_loaded) { + try addZirErrorMessages(&bundle, file); + } else if (file.zoir != null) { + try addZoirErrorMessages(&bundle, file); + } else { + // Either Zir or Zoir must have been loaded. + unreachable; + } } } for (zcu.failed_embed_files.values()) |error_msg| { @@ -3513,6 +3519,14 @@ 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); + 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, diff --git a/src/Zcu.zig b/src/Zcu.zig index 063e4e7e2b6d..d3264de47d35 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); @@ -660,6 +662,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. @@ -707,6 +711,7 @@ pub const File = struct { } pub fn unload(file: *File, gpa: Allocator) void { + if (file.zoir) |zoir| zoir.deinit(gpa); file.unloadTree(gpa); file.unloadSource(gpa); file.unloadZir(gpa); @@ -785,6 +790,14 @@ pub const File = struct { return &file.tree; } + pub fn getZoir(file: *File, gpa: Allocator) !*const Zoir { + if (file.zoir) |*zoir| return zoir; + assert(file.tree_loaded); + assert(file.tree.mode == .zon); + file.zoir = try ZonGen.generate(gpa, file.tree); + 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; diff --git a/src/zon.zig b/src/zon.zig index d2bd35ec2079..2f9f00d209f8 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -15,7 +15,6 @@ 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 ZonGen = std.zig.ZonGen; const Zoir = std.zig.Zoir; const LowerZon = @This(); @@ -24,7 +23,6 @@ sema: *Sema, file: *File, file_index: Zcu.File.Index, import_loc: LazySrcLoc, -zoir: Zoir, /// Lowers the given file as ZON. pub fn lower( @@ -34,15 +32,13 @@ pub fn lower( res_ty: Type, import_loc: LazySrcLoc, ) CompileError!InternPool.Index { - const ast = file.getTree(sema.gpa) catch unreachable; // Already validated - if (ast.errors.len != 0) { - return lowerAstErrors(file, sema, file_index); - } + assert(file.tree_loaded); + + const zoir = try file.getZoir(sema.gpa); - var zoir = try ZonGen.generate(sema.gpa, ast.*); - defer zoir.deinit(sema.gpa); if (zoir.hasCompileErrors()) { - return lowerZoirErrors(file, zoir, sema, file_index); + try sema.pt.zcu.failed_files.putNoClobber(sema.gpa, file, null); + return error.AnalysisFail; } const lower_zon: LowerZon = .{ @@ -50,10 +46,9 @@ pub fn lower( .file = file, .file_index = file_index, .import_loc = import_loc, - .zoir = zoir, }; - const data = ast.nodes.items(.data); + const data = file.tree.nodes.items(.data); const root = data[0].lhs; return lower_zon.lowerExpr(root, res_ty); } @@ -83,85 +78,6 @@ fn fail( return error.AnalysisFail; } -fn lowerAstErrors(file: *File, sema: *Sema, file_index: Zcu.File.Index) CompileError { - const tree = file.tree; - assert(tree.errors.len > 0); - - const gpa = sema.gpa; - const ip = &sema.pt.zcu.intern_pool; - const parse_err = tree.errors[0]; - - var buf: std.ArrayListUnmanaged(u8) = .{}; - defer buf.deinit(gpa); - - // Create the main error - buf.clearRetainingCapacity(); - try tree.renderError(parse_err, buf.writer(gpa)); - const err_msg = try Zcu.ErrorMsg.create( - gpa, - .{ - .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .{ .token_abs = parse_err.token + @intFromBool(parse_err.token_is_prev) }, - }, - "{s}", - .{buf.items}, - ); - - // Check for invalid bytes - const token_starts = tree.tokens.items(.start); - const token_tags = tree.tokens.items(.tag); - if (token_tags[parse_err.token + @intFromBool(parse_err.token_is_prev)] == .invalid) { - const bad_off: u32 = @intCast(tree.tokenSlice(parse_err.token + @intFromBool(parse_err.token_is_prev)).len); - const byte_abs = token_starts[parse_err.token + @intFromBool(parse_err.token_is_prev)] + bad_off; - try sema.pt.zcu.errNote( - .{ - .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .{ .byte_abs = byte_abs }, - }, - err_msg, - "invalid byte: '{'}'", - .{std.zig.fmtEscapes(tree.source[byte_abs..][0..1])}, - ); - } - - // Create the notes - for (tree.errors[1..]) |note| { - if (!note.is_note) break; - - buf.clearRetainingCapacity(); - try tree.renderError(note, buf.writer(gpa)); - try sema.pt.zcu.errNote( - .{ - .base_node_inst = try ip.trackZir(gpa, .main, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .{ .token_abs = note.token + @intFromBool(note.token_is_prev) }, - }, - err_msg, - "{s}", - .{buf.items}, - ); - } - - try sema.pt.zcu.failed_files.putNoClobber(gpa, file, err_msg); - return error.AnalysisFail; -} - -fn lowerZoirErrors(file: *File, zoir: Zoir, sema: *Sema, file_index: Zcu.File.Index) CompileError { - _ = file; - _ = zoir; - _ = sema; - _ = file_index; - @panic("unimplemented"); -} - const Ident = struct { bytes: []const u8, owned: bool, diff --git a/test/cases/compile_errors/@import_zon_addr_slice.zig b/test/cases/compile_errors/@import_zon_addr_slice.zig index dca859e6581f..48d50cbacf0d 100644 --- a/test/cases/compile_errors/@import_zon_addr_slice.zig +++ b/test/cases/compile_errors/@import_zon_addr_slice.zig @@ -8,5 +8,4 @@ pub fn main() void { // output_mode=Exe // imports=zon/addr_slice.zon // -// addr_slice.zon:2:14: error: expected type '[]const i32' -// tmp.zig:2:54: note: imported here +// addr_slice.zon:2:14: error: pointers are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_double_negation_float.zig b/test/cases/compile_errors/@import_zon_double_negation_float.zig index fdcbe5138ca2..a89088513b17 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_float.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_float.zig @@ -8,5 +8,4 @@ pub fn main() void { // output_mode=Exe // imports=zon/double_negation_float.zon // -// double_negation_float.zon:1:1: error: invalid ZON value -// tmp.zig:2:28: note: imported here +// 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 index 2201b09fdda6..07e888e390d1 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_int.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_int.zig @@ -8,5 +8,4 @@ pub fn main() void { // output_mode=Exe // imports=zon/double_negation_int.zon // -// double_negation_int.zon:1:1: error: expected type 'i32' -// tmp.zig:2:28: note: imported here +// 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 index c7d1dccf5c29..b4dc7b542bb5 100644 --- a/test/cases/compile_errors/@import_zon_enum_embedded_null.zig +++ b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig @@ -11,4 +11,3 @@ pub fn main() void { // imports=zon/enum_embedded_null.zon // // enum_embedded_null.zon:2:6: error: identifier cannot contain null bytes -// tmp.zig:4:40: 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 index a2bf474f63ea..6c1efffaba13 100644 --- a/test/cases/compile_errors/@import_zon_invalid_character.zig +++ b/test/cases/compile_errors/@import_zon_invalid_character.zig @@ -9,4 +9,3 @@ pub fn main() void { // imports=zon/invalid_character.zon // // invalid_character.zon:1:3: error: invalid escape character: 'a' -// tmp.zig:2:27: note: imported here diff --git a/test/cases/compile_errors/@import_zon_invalid_string.zig b/test/cases/compile_errors/@import_zon_invalid_string.zig index e103a4507447..331804a6f115 100644 --- a/test/cases/compile_errors/@import_zon_invalid_string.zig +++ b/test/cases/compile_errors/@import_zon_invalid_string.zig @@ -9,4 +9,3 @@ pub fn main() void { // imports=zon/invalid_string.zon // // invalid_string.zon:1:5: error: invalid escape character: 'a' -// tmp.zig:2:35: note: imported here diff --git a/test/cases/compile_errors/@import_zon_negative_zero.zig b/test/cases/compile_errors/@import_zon_negative_zero.zig index 5e935098a066..cb3499c98bf0 100644 --- a/test/cases/compile_errors/@import_zon_negative_zero.zig +++ b/test/cases/compile_errors/@import_zon_negative_zero.zig @@ -8,5 +8,6 @@ pub fn main() void { // output_mode=Exe // imports=zon/negative_zero.zon // -// negative_zero.zon:1:1: error: integer literal '-0' is ambiguous -// tmp.zig:2:27: note: imported here +// 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_type_decl.zig b/test/cases/compile_errors/@import_zon_type_decl.zig index 5d680249d2c0..687c3f195abf 100644 --- a/test/cases/compile_errors/@import_zon_type_decl.zig +++ b/test/cases/compile_errors/@import_zon_type_decl.zig @@ -8,5 +8,4 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_decl.zon // -// type_decl.zon:2:12: error: invalid ZON value -// tmp.zig:2:45: note: imported here +// 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 index 994b986ea96e..2074cd37d378 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_array.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_array.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_array.zon // -// type_expr_array.zon:1:1: error: ZON cannot contain type expressions -// tmp.zig:2:31: note: imported here +// 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 index dfc012339ffd..87c42ede6b46 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_fn.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_fn.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_fn.zon // -// type_expr_fn.zon:1:15: error: expected type 'i32' -// tmp.zig:2:28: note: imported here +// 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 index bedc9ea37715..6eedf32d4ad5 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_struct.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_struct.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_struct.zon // -// type_expr_struct.zon:1:1: error: ZON cannot contain type expressions -// tmp.zig:2:50: note: imported here +// 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 index 53bb497ff7de..17c8725ae6cd 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/type_expr_tuple.zon // -// type_expr_tuple.zon:1:1: error: ZON cannot contain type expressions -// tmp.zig:2:44: note: imported here +// 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_unescaped_newline.zig b/test/cases/compile_errors/@import_zon_unescaped_newline.zig index a342802ecb54..54fd1a5f86cb 100644 --- a/test/cases/compile_errors/@import_zon_unescaped_newline.zig +++ b/test/cases/compile_errors/@import_zon_unescaped_newline.zig @@ -9,4 +9,3 @@ pub fn main() void { // imports=zon/unescaped_newline.zon // // unescaped_newline.zon:1:1: error: expected expression, found 'invalid token' -// unescaped_newline.zon:1:3: note: invalid byte: '\n' diff --git a/test/cases/compile_errors/@import_zon_unknown_ident.zig b/test/cases/compile_errors/@import_zon_unknown_ident.zig index cbc37f3a7623..f3eb2e95ed79 100644 --- a/test/cases/compile_errors/@import_zon_unknown_ident.zig +++ b/test/cases/compile_errors/@import_zon_unknown_ident.zig @@ -8,5 +8,6 @@ pub fn main() void { // output_mode=Exe // imports=zon/unknown_ident.zon // -// unknown_ident.zon:2:14: error: expected type 'bool' -// tmp.zig:2:47: note: imported here +// 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 From 3ba08a0109d0aabd167b22b1dbd3a4ffaf93898e Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 16:19:36 -0800 Subject: [PATCH 30/51] Simplifies error handling --- lib/std/zon/parse.zig | 437 ++++++++++++++++++------------------------ 1 file changed, 187 insertions(+), 250 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index d1a618bde242..74a9f821c88b 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -25,168 +25,121 @@ pub const ParseOptions = struct { free_on_error: bool = true, }; -/// Information about the success or failure of a parse. -pub const Status = struct { - pub const TypeCheckError = struct { - token: Ast.TokenIndex, - message: []const u8, - }; - - pub const Error = union(enum) { - pub const Severity = enum { - @"error", - note, - }; - parse: struct { - ast: Ast, - err: Ast.Error, - }, - zon_gen_err: struct { - zoir: Zoir, - err: Zoir.CompileError, - ast: Ast, - }, - zon_gen_note: struct { - zoir: Zoir, - err: Zoir.CompileError.Note, - ast: Ast, - }, - type_check: struct { - err: TypeCheckError, - ast: Ast, - }, - - pub const Iterator = union(enum) { - parse: struct { - ast: Ast, - err_index: usize = 0, - }, - zon_gen: struct { - zoir: Zoir, - ast: Ast, - err_index: usize = 0, - note_index: ?usize = null, - }, - type_check: struct { - err: TypeCheckError, - err_index: usize = 0, - ast: Ast, - }, - none, - - pub fn next(self: *@This()) ?Error { - switch (self.*) { - .parse => |*iter| { - if (iter.err_index >= iter.ast.errors.len) return null; - const curr = iter.err_index; - iter.err_index += 1; - return .{ .parse = .{ - .ast = iter.ast, - .err = iter.ast.errors[curr], - } }; - }, - .zon_gen => |*iter| { - if (iter.err_index >= iter.zoir.compile_errors.len) return null; - const err = iter.zoir.compile_errors[iter.err_index]; - - // If we're iterating notes for an error, try to return the next one. If - // there are no more recurse and try the next error. - if (iter.note_index) |*note_index| { - if (note_index.* < err.note_count) { - const note = err.getNotes(iter.zoir)[note_index.*]; - note_index.* += 1; - return .{ .zon_gen_note = .{ - .err = note, - .zoir = iter.zoir, - .ast = iter.ast, - } }; - } else { - iter.note_index = null; - iter.err_index += 1; - return self.next(); - } - } - - // Return the next error, next time try returning notes. - iter.note_index = 0; - return .{ .zon_gen_err = .{ - .zoir = iter.zoir, - .err = err, - .ast = iter.ast, - } }; - }, - .type_check => |*iter| { - if (iter.err_index > 0) return null; - iter.err_index += 1; - return .{ .type_check = .{ - .err = iter.err, - .ast = iter.ast, - } }; +pub const Error = union(enum) { + zoir: Zoir.CompileError, + type_check: TypeCheckFailure, + + pub const Note = union(enum) { + zoir: Zoir.CompileError.Note, + + pub const Iterator = struct { + index: usize = 0, + err: Error, + status: *const Status, + + pub fn next(self: *@This()) ?Note { + switch (self.err) { + .zoir => |err| { + if (self.index >= err.note_count) return null; + const zoir = self.status.zoir.?; + const note = err.getNotes(zoir)[self.index]; + self.index += 1; + return .{ .zoir = note }; }, - .none => return null, + .type_check => return null, } } }; - pub fn getSeverity(self: @This()) Severity { - return switch (self) { - .parse => |kind| if (kind.err.is_note) .note else .@"error", - .zon_gen_err => .@"error", - .zon_gen_note => .note, - .type_check => .@"error", - }; + pub fn getMessage(self: Note, status: *const Status) []const u8 { + switch (self) { + .zoir => |note| return note.msg.get(status.zoir.?), + } } - pub fn getLocation(self: @This()) Ast.Location { + pub fn getLocation(self: Note, status: *const Status) Ast.Location { switch (self) { - .parse => |kind| { - const offset = kind.ast.errorOffset(kind.err); - return kind.ast.tokenLocation(offset, kind.err.token); - }, - inline .zon_gen_err, .zon_gen_note => |kind| { - if (kind.err.token == Zoir.CompileError.invalid_token) { - const main_tokens = kind.ast.nodes.items(.main_token); - const ast_node = kind.err.node_or_offset; - const token = main_tokens[ast_node]; - return kind.ast.tokenLocation(0, token); - } else { - var location = kind.ast.tokenLocation(0, kind.err.token); - location.column += kind.err.node_or_offset; - return location; - } - }, - .type_check => |kind| return kind.ast.tokenLocation(0, kind.err.token), + .zoir => |note| return zoirErrorLocation( + status.ast.?, + note.token, + note.node_or_offset, + ), } } + }; - pub fn fmtMessage(self: @This()) std.fmt.Formatter(formatMessage) { - return .{ .data = self }; - } + pub const Iterator = struct { + index: usize = 0, + status: *const Status, - fn formatMessage( - self: @This(), - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - _ = fmt; - switch (self) { - .parse => |kind| try kind.ast.renderError(kind.err, writer), - inline .zon_gen_err, .zon_gen_note => |kind| { - try writer.writeAll(kind.err.msg.get(kind.zoir)); - }, - .type_check => |kind| try writer.writeAll(kind.err.message), + 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; } }; - /// The AST, which may or may not contain errors. + const TypeCheckFailure = struct { + token: Ast.TokenIndex, + message: []const u8, + }; + + pub fn getMessage(self: @This(), status: *const Status) []const u8 { + return switch (self) { + .zoir => |err| err.msg.get(status.zoir.?), + .type_check => |err| err.message, + }; + } + + 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(0, 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, - /// The Zoir, which may or may not contain errors. zoir: ?Zoir = null, - /// The type check error if one occurred. - type_check: ?TypeCheckError = null, + type_check: ?Error.TypeCheckFailure = null, fn assertEmpty(self: Status) void { assert(self.ast == null); @@ -201,31 +154,7 @@ pub const Status = struct { } pub fn iterateErrors(self: *const Status) Error.Iterator { - const ast = self.ast orelse return .none; - - if (ast.errors.len > 0) { - return .{ .parse = .{ - .ast = ast, - } }; - } - - if (self.zoir) |zoir| { - if (zoir.hasCompileErrors()) { - return .{ .zon_gen = .{ - .zoir = zoir, - .ast = ast, - } }; - } - } - - if (self.type_check) |type_check| { - return .{ .type_check = .{ - .err = type_check, - .ast = ast, - } }; - } - - return .none; + return .{ .status = self }; } pub fn format( @@ -236,19 +165,18 @@ pub const Status = struct { ) !void { _ = fmt; _ = options; - - var first = true; var errors = self.iterateErrors(); while (errors.next()) |err| { - if (!first) { - try writer.writeByte('\n'); - } else { - first = false; + const loc = err.getLocation(self); + const msg = err.getMessage(self); + try writer.print("{}:{}: error: {s}\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.getMessage(self); + try writer.print("{}:{}: note: {s}\n", .{ note_loc.line + 1, note_loc.column + 1, note_msg }); } - const loc = err.getLocation(); - const msg = err.fmtMessage(); - const severity = @tagName(err.getSeverity()); - try writer.print("{}:{}: {s}: {}", .{ loc.line + 1, loc.column + 1, severity, msg }); } } }; @@ -258,11 +186,11 @@ test "std.zon ast errors" { const gpa = std.testing.allocator; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{})); - try std.testing.expectFmt( - \\1:13: error: expected ',' after initializer - \\1:13: error: expected field initializer - , "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), + ); + try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status}); } test "std.zon comments" { @@ -283,7 +211,7 @@ test "std.zon comments" { \\// comment , &status, .{})); try std.testing.expectFmt( - "1:1: error: expected expression, found 'a document comment'", + "1:1: error: expected expression, found 'a document comment'\n", "{}", .{status}, ); @@ -337,7 +265,6 @@ pub fn parseFromSlice( var ast = try std.zig.Ast.parse(gpa, source, .zon); defer if (status == null) ast.deinit(gpa); if (status) |s| s.ast = ast; - if (ast.errors.len != 0) return error.ParseZon; var zoir = try ZonGen.generate(gpa, ast); defer if (status == null) zoir.deinit(gpa); @@ -403,7 +330,7 @@ pub fn parseFromZoirNode( s.zoir = zoir; } - if (zoir.hasCompileErrors() or ast.errors.len > 0) { + if (zoir.hasCompileErrors()) { return error.ParseZon; } @@ -724,7 +651,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.z=2.5}", &status, .{})); - try std.testing.expectFmt("1:4: error: unexpected field, supported fields: x, y", "{}", .{status}); + try std.testing.expectFmt("1:4: error: unexpected field, supported fields: x, y\n", "{}", .{status}); } // Explicit void field @@ -733,7 +660,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x=1}", &status, .{})); - try std.testing.expectFmt("1:6: error: void union field not expressed as enum literal", "{}", .{status}); + try std.testing.expectFmt("1:6: error: void union field not expressed as enum literal\n", "{}", .{status}); } // Extra field @@ -742,7 +669,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // No fields @@ -751,7 +678,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // Enum literals cannot coerce into untagged unions @@ -760,7 +687,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); - try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } // Unknown field for enum literal coercion @@ -769,7 +696,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".y", &status, .{})); - try std.testing.expectFmt("1:2: error: unexpected field, supported fields: x", "{}", .{status}); + try std.testing.expectFmt("1:2: error: unexpected field, supported fields: x\n", "{}", .{status}); } // Non void field for enum literal coercion @@ -778,7 +705,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); - try std.testing.expectFmt("1:2: error: expected union", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } } @@ -910,7 +837,7 @@ test "std.zon structs" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); - try std.testing.expectFmt("1:12: error: unexpected field, supported fields: x, y", "{}", .{status}); + try std.testing.expectFmt("1:12: error: unexpected field, supported fields: x, y\n", "{}", .{status}); } // Duplicate field @@ -919,7 +846,7 @@ test "std.zon structs" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{})); - try std.testing.expectFmt("1:12: error: duplicate field", "{}", .{status}); + try std.testing.expectFmt("1:12: error: duplicate field\n", "{}", .{status}); } // Ignore unknown fields @@ -937,7 +864,7 @@ test "std.zon structs" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); - try std.testing.expectFmt("1:4: error: unexpected field, no fields expected", "{}", .{status}); + try std.testing.expectFmt("1:4: error: unexpected field, no fields expected\n", "{}", .{status}); } // Missing field @@ -946,7 +873,7 @@ test "std.zon structs" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{})); - try std.testing.expectFmt("1:2: error: missing required field y", "{}", .{status}); + try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status}); } // Default field @@ -982,6 +909,7 @@ test "std.zon structs" { try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' + \\ , "{}", .{status}); } @@ -994,6 +922,7 @@ test "std.zon structs" { try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' + \\ , "{}", .{status}); } @@ -1006,6 +935,7 @@ test "std.zon structs" { try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' + \\ , "{}", .{status}); } @@ -1018,6 +948,7 @@ test "std.zon structs" { try std.testing.expectFmt( \\1:1: error: types are not available in ZON \\1:1: note: replace the type with '.' + \\ , "{}", .{status}); } @@ -1030,6 +961,7 @@ test "std.zon structs" { try std.testing.expectFmt( \\1:9: error: types are not available in ZON \\1:9: note: replace the type with '.' + \\ , "{}", .{status}); } } @@ -1106,7 +1038,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); } // Extra field @@ -1115,7 +1047,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); } // Tuple with unexpected field names @@ -1124,7 +1056,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 1 field", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 1 field\n", "{}", .{status}); } // Struct with missing field names @@ -1133,7 +1065,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, gpa, ".{10.0}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected struct", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status}); } } @@ -1275,7 +1207,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 0 fields", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 0 fields\n", "{}", .{status}); } // Expect 1 find 2 @@ -1283,7 +1215,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 1 field", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 1 field\n", "{}", .{status}); } // Expect 2 find 1 @@ -1291,7 +1223,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); } // Expect 3 find 0 @@ -1299,7 +1231,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, ".{}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 3 fields", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple with 3 fields\n", "{}", .{status}); } // Wrong inner type @@ -1309,7 +1241,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: expected bool", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected bool\n", "{}", .{status}); } // Slice @@ -1317,7 +1249,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: expected bool", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected bool\n", "{}", .{status}); } } @@ -1328,7 +1260,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, "'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple with 3 fields", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple with 3 fields\n", "{}", .{status}); } // Slice @@ -1336,7 +1268,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, "'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1345,7 +1277,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: pointers are not available in ZON", "{}", .{status}); + try std.testing.expectFmt("1:3: error: pointers are not available in ZON\n", "{}", .{status}); } } @@ -1460,7 +1392,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]u8, gpa, "\"abcd\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { @@ -1470,7 +1402,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]u8, gpa, "\\\\abcd", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1487,7 +1419,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple with 4 fields", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple with 4 fields\n", "{}", .{status}); } { @@ -1497,7 +1429,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple with 4 fields", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple with 4 fields\n", "{}", .{status}); } } @@ -1527,7 +1459,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { @@ -1537,7 +1469,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1549,7 +1481,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]const u8, gpa, "true", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected string", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected string\n", "{}", .{status}); } // Expecting string literal, getting an incompatible tuple @@ -1560,7 +1492,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]const u8, gpa, ".{false}", &status, .{}), ); - try std.testing.expectFmt("1:3: error: expected u8", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected u8\n", "{}", .{status}); } // Invalid string literal @@ -1571,7 +1503,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), ); - try std.testing.expectFmt("1:3: error: invalid escape character: 'a'", "{}", .{status}); + try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{}", .{status}); } // Slice wrong child type @@ -1580,14 +1512,14 @@ test "std.zon string literal" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\"a\"", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\\\\a", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1597,14 +1529,14 @@ test "std.zon string literal" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1684,7 +1616,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".qux", &status, .{})); try std.testing.expectFmt( - "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", + "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", "{}", .{status}, ); @@ -1696,7 +1628,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{})); try std.testing.expectFmt( - "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"", + "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", "{}", .{status}, ); @@ -1707,7 +1639,7 @@ test "std.zon enum literals" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, "true", &status, .{})); - try std.testing.expectFmt("1:1: error: expected enum literal", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status}); } // Test embedded nulls in an identifier @@ -1718,7 +1650,7 @@ test "std.zon enum literals" { error.ParseZon, parseFromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), ); - try std.testing.expectFmt("1:2: error: identifier cannot contain null bytes", "{}", .{status}); + try std.testing.expectFmt("1:2: error: identifier cannot contain null bytes\n", "{}", .{status}); } } @@ -1848,13 +1780,14 @@ test "std.zon parse bool" { \\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, parseFromSlice(bool, gpa, "123", &status, .{})); - try std.testing.expectFmt("1:1: error: expected bool", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected bool\n", "{}", .{status}); } } @@ -1994,13 +1927,13 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "36893488147419103232", &status, .{})); - try std.testing.expectFmt("1:1: error: i66 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: i66 cannot represent value\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "-36893488147419103233", &status, .{})); - try std.testing.expectFmt("1:1: error: i66 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: i66 cannot represent value\n", "{}", .{status}); } // Test parsing whole number floats as integers @@ -2085,7 +2018,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "32a32", &status, .{})); - try std.testing.expectFmt("1:3: error: invalid digit 'a' for decimal base", "{}", .{status}); + try std.testing.expectFmt("1:3: error: invalid digit 'a' for decimal base\n", "{}", .{status}); } // Failing to parse as int @@ -2093,7 +2026,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "true", &status, .{})); - try std.testing.expectFmt("1:1: error: expected u8", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected u8\n", "{}", .{status}); } // Failing because an int is out of range @@ -2101,7 +2034,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "256", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); } // Failing because a negative int is out of range @@ -2109,7 +2042,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-129", &status, .{})); - try std.testing.expectFmt("1:1: error: i8 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: i8 cannot represent value\n", "{}", .{status}); } // Failing because an unsigned int is negative @@ -2117,7 +2050,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); } // Failing because a float is non-whole @@ -2125,7 +2058,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "1.5", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); } // Failing because a float is negative @@ -2133,7 +2066,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1.0", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value", "{}", .{status}); + try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); } // Negative integer zero @@ -2145,6 +2078,7 @@ test "std.zon parse int" { \\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}); } @@ -2157,6 +2091,7 @@ test "std.zon parse int" { \\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}); } @@ -2169,14 +2104,14 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "--2", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{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, parseFromSlice(f32, gpa, "--2.0", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); } // Invalid int literal @@ -2184,7 +2119,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "0xg", &status, .{})); - try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base", "{}", .{status}); + try std.testing.expectFmt("1:3: error: invalid digit 'g' for hex base\n", "{}", .{status}); } // Notes on invalid int literal @@ -2195,6 +2130,7 @@ test "std.zon parse int" { try std.testing.expectFmt( \\1:1: error: number '0123' has leading zero \\1:1: note: use '0o' prefix for octal literals + \\ , "{}", .{status}); } } @@ -2206,13 +2142,13 @@ test "std.zon negative char" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{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, parseFromSlice(i16, gpa, "-'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); } } @@ -2280,7 +2216,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); } // nan as int not allowed @@ -2288,7 +2224,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); } // nan as int not allowed @@ -2296,7 +2232,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); } // inf as int not allowed @@ -2304,7 +2240,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "inf", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); } // -inf as int not allowed @@ -2312,7 +2248,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-inf", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); } // Bad identifier as float @@ -2324,6 +2260,7 @@ test "std.zon parse float" { \\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}); } @@ -2331,7 +2268,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-foo", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); } // Non float as float @@ -2339,7 +2276,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "\"foo\"", &status, .{})); - try std.testing.expectFmt("1:1: error: expected f32", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected f32\n", "{}", .{status}); } } From ec8bca98ad9bd5e0bd8153610031e5cdc6961bb5 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 18:08:17 -0800 Subject: [PATCH 31/51] Ports import zon to use Zoir --- src/zon.zig | 1025 ++++++++--------- .../@import_zon_coerce_pointer.zig | 2 +- 2 files changed, 458 insertions(+), 569 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 2f9f00d209f8..9cd839817849 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -48,9 +48,7 @@ pub fn lower( .import_loc = import_loc, }; - const data = file.tree.nodes.items(.data); - const root = data[0].lhs; - return lower_zon.lowerExpr(root, res_ty); + return lower_zon.lowerExpr(.root, res_ty); } fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { @@ -69,10 +67,22 @@ fn fail( loc: LazySrcLoc.Offset, comptime format: []const u8, args: anytype, +) (Allocator.Error || error{AnalysisFail}) { + @branchHint(.cold); + return self.failWithNote(loc, format, args, null); +} + +fn failWithNote( + self: LowerZon, + loc: LazySrcLoc.Offset, + comptime format: []const u8, + args: anytype, + note: ?[]const u8, ) (Allocator.Error || error{AnalysisFail}) { @branchHint(.cold); const src_loc = try self.lazySrcLoc(loc); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); + if (note) |n| try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "{s}", .{n}); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); return error.AnalysisFail; @@ -181,9 +191,8 @@ const FieldTypes = union(enum) { } }; -fn lowerExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!InternPool.Index { +fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { - .void => return self.lowerVoid(node), .bool => return self.lowerBool(node), .int, .comptime_int => return self.lowerInt(node, res_ty), .float, .comptime_float => return self.lowerFloat(node, res_ty), @@ -206,408 +215,326 @@ fn lowerExpr(self: LowerZon, node: Ast.Node.Index, res_ty: Type) CompileError!In .frame, .@"anyframe", .vector, - => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), - } -} - -fn lowerVoid(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { - const tags = self.file.tree.nodes.items(.tag); - const data = self.file.tree.nodes.items(.data); - - if (tags[node] == .block_two and data[node].lhs == 0 and data[node].rhs == 0) { - return .void_value; + .void, + => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' not available in ZON", + .{res_ty.fmt(self.sema.pt)}, + ), } - - return self.fail(.{ .node_abs = node }, "expected type 'void'", .{}); } -fn lowerBool(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { - const gpa = self.sema.gpa; - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - - if (tags[node] == .identifier) { - const token = main_tokens[node]; - var litIdent = try self.ident(token); - defer litIdent.deinit(gpa); - - const BoolIdent = enum { true, false }; - const values = std.StaticStringMap(BoolIdent).initComptime(.{ - .{ "true", .true }, - .{ "false", .false }, - }); - if (values.get(litIdent.bytes)) |value| { - return switch (value) { - .true => .bool_true, - .false => .bool_false, - }; - } - } - return self.fail(.{ .node_abs = node }, "expected type 'bool'", .{}); +fn lowerBool(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { + return switch (node.get(self.file.zoir.?)) { + .true => .bool_true, + .false => .bool_false, + else => self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type 'bool'", + .{}, + ), + }; } fn lowerInt( self: LowerZon, - node: Ast.Node.Index, + node: Zoir.Node.Index, res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - const gpa = self.sema.gpa; - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - const num_lit_node, const is_negative = if (tags[node] == .negation) b: { - const data = self.file.tree.nodes.items(.data); - break :b .{ - data[node].lhs, - node, - }; - } else .{ - node, - null, - }; - switch (tags[num_lit_node]) { - .char_literal => { - const token = main_tokens[num_lit_node]; - const token_bytes = self.file.tree.tokenSlice(token); - const char = switch (std.zig.string_literal.parseCharLiteral(token_bytes)) { - .success => |char| char, - .failure => |err| { - const offset = self.file.tree.tokens.items(.start)[token]; - return self.fail( - .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, - "{}", - .{err.fmtWithSource(token_bytes)}, + 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 (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const lhs_info = res_ty.intInfo(self.sema.pt.zcu); + + // If lhs is unsigned and rhs is less than 0, we're out of bounds + if (lhs_info.signedness == .unsigned and rhs < 0) return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, ); - }, - }; - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ - .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .i64 = if (is_negative == null) char else -@as(i64, char) }, - }, - }); - }, - .number_literal => { - const token = main_tokens[num_lit_node]; - const token_bytes = self.file.tree.tokenSlice(token); - const parsed = std.zig.number_literal.parseNumberLiteral(token_bytes); - switch (parsed) { - .int => |unsigned| { - if (is_negative) |negative_node| { - if (unsigned == 0) { - return self.fail(.{ .node_abs = negative_node }, "integer literal '-0' is ambiguous", .{}); - } - const signed = std.math.negateCast(unsigned) catch { - var result = try std.math.big.int.Managed.initSet(gpa, unsigned); - defer result.deinit(); - result.negate(); - - if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { - const int_info = res_ty.intInfo(self.sema.pt.zcu); - if (!result.fitsInTwosComp(int_info.signedness, int_info.bits)) { - return self.fail( - .{ .node_abs = num_lit_node }, - "type '{}' cannot represent integer value '-{}'", - .{ res_ty.fmt(self.sema.pt), unsigned }, - ); - } - } - - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .big_int = result.toConst() }, - } }); - }; - - if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { - const int_info = res_ty.intInfo(self.sema.pt.zcu); - if (std.math.cast(u6, int_info.bits)) |bits| { - const min_int: i64 = if (int_info.signedness == .unsigned) 0 else -(@as(i64, 1) << (bits - 1)); - if (signed < min_int) { - return self.fail( - .{ .node_abs = num_lit_node }, - "type '{}' cannot represent integer value '{}'", - .{ res_ty.fmt(self.sema.pt), unsigned }, - ); - } - } - } - - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .i64 = signed }, - } }); - } else { - if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { - const int_info = res_ty.intInfo(self.sema.pt.zcu); - if (std.math.cast(u6, int_info.bits)) |bits| { - const max_int: u64 = (@as(u64, 1) << (bits - @intFromBool(int_info.signedness == .signed))) - 1; - if (unsigned > max_int) { - return self.fail( - .{ .node_abs = num_lit_node }, - "type '{}' cannot represent integer value '{}'", - .{ res_ty.fmt(self.sema.pt), unsigned }, - ); - } - } - } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .u64 = unsigned }, - } }); - } - }, - .big_int => |base| { - var big_int = try std.math.big.int.Managed.init(gpa); - defer big_int.deinit(); - - const prefix_offset: usize = if (base == .decimal) 0 else 2; - big_int.setString(@intFromEnum(base), token_bytes[prefix_offset..]) catch |err| switch (err) { - error.InvalidCharacter => unreachable, // caught in `parseNumberLiteral` - error.InvalidBase => unreachable, // we only pass 16, 8, 2, see above - error.OutOfMemory => return error.OutOfMemory, - }; - - assert(big_int.isPositive()); - if (is_negative != null) big_int.negate(); - - if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { - const int_info = res_ty.intInfo(self.sema.pt.zcu); - if (!big_int.fitsInTwosComp(int_info.signedness, int_info.bits)) { + // If lhs has less than the 32 bits rhs can hold, we need to check the max and + // min values + if (std.math.cast(u5, lhs_info.bits)) |bits| { + const min_int: i32 = if (lhs_info.signedness == .unsigned or bits == 0) b: { + break :b 0; + } else b: { + break :b -(@as(i32, 1) << (bits - 1)); + }; + const max_int: i32 = if (bits == 0) b: { + break :b 0; + } else b: { + break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + }; + if (rhs < min_int or rhs > max_int) { return self.fail( - .{ .node_abs = num_lit_node }, + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, "type '{}' cannot represent integer value '{}'", - .{ res_ty.fmt(self.sema.pt), big_int }, + .{ res_ty.fmt(self.sema.pt), rhs }, ); } } + } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .big_int = big_int.toConst() }, - } }); - }, - .float => { - const unsigned_float = std.fmt.parseFloat(f128, token_bytes) catch { - // Validated by tokenizer - unreachable; - }; - const float = if (is_negative == null) unsigned_float else -unsigned_float; - - // Check for fractional components - if (@rem(float, 1) != 0) { + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = rhs }, + } }); + }, + .big => |val| { + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) { return self.fail( - .{ .node_abs = num_lit_node }, - "fractional component prevents float value '{}' from coercion to type '{}'", - .{ float, res_ty.fmt(self.sema.pt) }, + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), val }, ); } + } - // Create a rational representation of the float - var rational = try std.math.big.Rational.init(gpa); - defer rational.deinit(); - rational.setFloat(f128, float) catch |err| switch (err) { - error.NonFiniteFloat => unreachable, - error.OutOfMemory => return error.OutOfMemory, - }; + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = val }, + } }); + }, + }, + .float_literal => |val| { + // Check for fractional components + if (@rem(val, 1) != 0) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "fractional component prevents float value '{}' from coercion to type '{}'", + .{ val, res_ty.fmt(self.sema.pt) }, + ); + } - // 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)); - if (is_negative != null) rational.negate(); + // Create a rational representation of the float + var rational = try std.math.big.Rational.init(gpa); + defer rational.deinit(); + rational.setFloat(f128, val) catch |err| switch (err) { + error.NonFiniteFloat => unreachable, + error.OutOfMemory => return error.OutOfMemory, + }; - // 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)) { + // The float is reduced in rational.setFloat, so we assert that denominator is equal to + // one + const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true }; + assert(rational.q.toConst().eqlAbs(big_one)); + + // Check that the result is in range of the result type + const int_info = res_ty.intInfo(self.sema.pt.zcu); + if (!rational.p.fitsInTwosComp(int_info.signedness, int_info.bits)) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "float value '{}' cannot be stored in integer type '{}'", + .{ val, res_ty.fmt(self.sema.pt) }, + ); + } + + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .big_int = rational.p.toConst() }, + }, + }); + }, + .char_literal => |val| { + const rhs: u32 = val; + // If our result is a fixed size integer, check that our value is not out of bounds + if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + const lhs_info = res_ty.intInfo(self.sema.pt.zcu); + // If lhs has less than 64 bits, we bounds check. We check at 64 instead of 32 in + // case LHS is signed. + if (std.math.cast(u6, lhs_info.bits)) |bits| { + const max_int: i64 = if (bits == 0) b: { + break :b 0; + } else b: { + break :b (@as(i64, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + }; + if (rhs > max_int) { return self.fail( - .{ .node_abs = num_lit_node }, - "float value '{}' cannot be stored in integer type '{}'", - .{ float, res_ty.fmt(self.sema.pt) }, + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "type '{}' cannot represent integer value '{}'", + .{ res_ty.fmt(self.sema.pt), rhs }, ); } - - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ - .int = .{ - .ty = res_ty.toIntern(), - .storage = .{ .big_int = rational.p.toConst() }, - }, - }); - }, - .failure => |err| return self.failWithNumberError(token, err), + } } + return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + .int = .{ + .ty = res_ty.toIntern(), + .storage = .{ .i64 = rhs }, + }, + }); }, - .identifier => { - unreachable; // Decide what error to give here - }, - else => return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), - } + + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; } fn lowerFloat( self: LowerZon, - node: Ast.Node.Index, + node: Zoir.Node.Index, res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - const num_lit_node, const is_negative = if (tags[node] == .negation) b: { - const data = self.file.tree.nodes.items(.data); - break :b .{ - data[node].lhs, - node, - }; - } else .{ - node, - null, - }; - switch (tags[num_lit_node]) { - .char_literal => { - const token = main_tokens[num_lit_node]; - const token_bytes = self.file.tree.tokenSlice(token); - var char: i64 = switch (std.zig.string_literal.parseCharLiteral(token_bytes)) { - .success => |char| char, - .failure => |err| { - const offset = self.file.tree.tokens.items(.start)[token]; - return self.fail( - .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, - "{}", - .{err.fmtWithSource(token_bytes)}, - ); - }, - }; - if (is_negative != null) char = -char; - return self.sema.pt.intern(.{ .float = .{ + switch (node.get(self.file.zoir.?)) { + .int_literal => |int| switch (int) { + .small => |val| return self.sema.pt.intern(.{ .float = .{ .ty = res_ty.toIntern(), .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = @floatFromInt(char) }, - .f32_type => .{ .f32 = @floatFromInt(char) }, - .f64_type => .{ .f64 = @floatFromInt(char) }, - .f80_type => .{ .f80 = @floatFromInt(char) }, - .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(char) }, + .f16_type => .{ .f16 = @floatFromInt(val) }, + .f32_type => .{ .f32 = @floatFromInt(val) }, + .f64_type => .{ .f64 = @floatFromInt(val) }, + .f80_type => .{ .f80 = @floatFromInt(val) }, + .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(val) }, else => unreachable, }, - } }); - }, - .number_literal => { - const token = main_tokens[num_lit_node]; - const token_bytes = self.file.tree.tokenSlice(token); - - var float = std.fmt.parseFloat(f128, token_bytes) catch |err| switch (err) { - error.InvalidCharacter => return self.fail(.{ .node_abs = num_lit_node }, "invalid character", .{}), - }; - if (is_negative != null) float = -float; - - return self.sema.pt.intern(.{ - .float = .{ + } }), + .big => { + const main_tokens = self.file.tree.nodes.items(.main_token); + const tags = self.file.tree.nodes.items(.tag); + const data = self.file.tree.nodes.items(.data); + const ast_node = node.getAstNode(self.file.zoir.?); + const negative = tags[ast_node] == .negation; + const num_lit_node = if (negative) data[ast_node].lhs else ast_node; + const token = main_tokens[num_lit_node]; + const bytes = self.file.tree.tokenSlice(token); + const val = std.fmt.parseFloat(f128, bytes) catch { + // Bytes already validated by big int parser + unreachable; + }; + return self.sema.pt.intern(.{ .float = .{ .ty = res_ty.toIntern(), .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = @floatCast(float) }, - .f32_type => .{ .f32 = @floatCast(float) }, - .f64_type => .{ .f64 = @floatCast(float) }, - .f80_type => .{ .f80 = @floatCast(float) }, - .f128_type, .comptime_float_type => .{ .f128 = float }, + .f16_type => .{ .f16 = @floatCast(val) }, + .f32_type => .{ .f32 = @floatCast(val) }, + .f64_type => .{ .f64 = @floatCast(val) }, + .f80_type => .{ .f80 = @floatCast(val) }, + .f128_type, .comptime_float_type => .{ .f128 = val }, else => unreachable, }, - }, - }); - }, - .identifier => { - switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { - .float, .comptime_float => {}, - else => return self.fail(.{ .node_abs = num_lit_node }, "invalid ZON value", .{}), - } - const token = main_tokens[num_lit_node]; - const bytes = self.file.tree.tokenSlice(token); - const LitIdent = enum { nan, inf }; - const values = std.StaticStringMap(LitIdent).initComptime(.{ - .{ "nan", .nan }, - .{ "inf", .inf }, - }); - if (values.get(bytes)) |value| { - return switch (value) { - .nan => self.sema.pt.intern(.{ - .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = std.math.nan(f16) }, - .f32_type => .{ .f32 = std.math.nan(f32) }, - .f64_type => .{ .f64 = std.math.nan(f64) }, - .f80_type => .{ .f80 = std.math.nan(f80) }, - .f128_type, .comptime_float_type => .{ .f128 = std.math.nan(f128) }, - else => unreachable, - }, - }, - }), - .inf => self.sema.pt.intern(.{ - .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = if (is_negative == null) std.math.inf(f16) else -std.math.inf(f16) }, - .f32_type => .{ .f32 = if (is_negative == null) std.math.inf(f32) else -std.math.inf(f32) }, - .f64_type => .{ .f64 = if (is_negative == null) std.math.inf(f64) else -std.math.inf(f64) }, - .f80_type => .{ .f80 = if (is_negative == null) std.math.inf(f80) else -std.math.inf(f80) }, - .f128_type, .comptime_float_type => .{ .f128 = if (is_negative == null) std.math.inf(f128) else -std.math.inf(f128) }, - else => unreachable, - }, - }, - }), - }; - } - return self.fail(.{ .node_abs = num_lit_node }, "use of unknown identifier '{s}'", .{bytes}); + } }); + }, }, - else => return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}), + .float_literal => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatCast(val) }, + .f32_type => .{ .f32 = @floatCast(val) }, + .f64_type => .{ .f64 = @floatCast(val) }, + .f80_type => .{ .f80 = @floatCast(val) }, + .f128_type, .comptime_float_type => .{ .f128 = val }, + else => unreachable, + }, + } }), + .pos_inf => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = std.math.inf(f16) }, + .f32_type => .{ .f32 = std.math.inf(f32) }, + .f64_type => .{ .f64 = std.math.inf(f64) }, + .f80_type => .{ .f80 = std.math.inf(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = std.math.inf(f128) }, + else => unreachable, + }, + } }), + .neg_inf => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = -std.math.inf(f16) }, + .f32_type => .{ .f32 = -std.math.inf(f32) }, + .f64_type => .{ .f64 = -std.math.inf(f64) }, + .f80_type => .{ .f80 = -std.math.inf(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = -std.math.inf(f128) }, + else => unreachable, + }, + } }), + .nan => return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = std.math.nan(f16) }, + .f32_type => .{ .f32 = std.math.nan(f32) }, + .f64_type => .{ .f64 = std.math.nan(f64) }, + .f80_type => .{ .f80 = std.math.nan(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = std.math.nan(f128) }, + else => unreachable, + }, + } }), + .char_literal => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = @floatFromInt(val) }, + .f32_type => .{ .f32 = @floatFromInt(val) }, + .f64_type => .{ .f64 = @floatFromInt(val) }, + .f80_type => .{ .f80 = @floatFromInt(val) }, + .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(val) }, + else => unreachable, + }, + } }), + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), } } -fn lowerOptional(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - - if (tags[node] == .identifier) { - const token = main_tokens[node]; - const bytes = self.file.tree.tokenSlice(token); - if (std.mem.eql(u8, bytes, "null")) return .null_value; - } - - return self.sema.pt.intern(.{ .opt = .{ - .ty = res_ty.toIntern(), - .val = try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), - } }); +fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + return switch (node.get(self.file.zoir.?)) { + .null => .null_value, + else => try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), + }; } -fn lowerNull(self: LowerZon, node: Ast.Node.Index) !InternPool.Index { - const tags = self.file.tree.nodes.items(.tag); - const main_tokens = self.file.tree.nodes.items(.main_token); - - if (tags[node] == .identifier) { - const token = main_tokens[node]; - const bytes = self.file.tree.tokenSlice(token); - if (std.mem.eql(u8, bytes, "null")) return .null_value; +fn lowerNull(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { + switch (node.get(self.file.zoir.?)) { + .null => return .null_value, + else => return self.fail(.{ .node_abs = node.getAstNode(self.file.zoir.?) }, "expected null", .{}), } - - return self.fail(.{ .node_abs = node }, "invalid ZON value", .{}); } -fn lowerArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const gpa = self.sema.gpa; const array_info = res_ty.arrayInfo(self.sema.pt.zcu); - var buf: [2]NodeIndex = undefined; - const elem_nodes = try self.elements(res_ty, &buf, node); + const nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; - if (elem_nodes.len != array_info.len) { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + if (nodes.len != array_info.len) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); } - const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(array_info.sentinel != null)); + const elems = try gpa.alloc( + InternPool.Index, + nodes.len + @intFromBool(array_info.sentinel != null), + ); defer gpa.free(elems); - for (elem_nodes, 0..) |elem_node, i| { - elems[i] = try self.lowerExpr(elem_node, array_info.elem_type); + for (0..nodes.len) |i| { + elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type); } if (array_info.sentinel) |sentinel| { @@ -620,76 +547,108 @@ fn lowerArray(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } -fn lowerEnum(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const main_tokens = self.file.tree.nodes.items(.main_token); - const tags = self.file.tree.nodes.items(.tag); +fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; + switch (node.get(self.file.zoir.?)) { + .enum_literal => |field_name| { + const field_name_interned = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + field_name.get(self.file.zoir.?), + .no_embedded_nulls, + ); + const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "enum {} has no member named '{}'", + .{ + res_ty.fmt(self.sema.pt), + std.zig.fmtId(field_name.get(self.file.zoir.?)), + }, + ); + }; - if (tags[node] != .enum_literal) { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); - } - - const field_name = try self.identAsNullTerminatedString(main_tokens[node]); - const field_index = res_ty.enumFieldIndex(field_name, self.sema.pt.zcu) orelse { - return self.fail(.{ .node_abs = node }, "enum {} has no member named '{}'", .{ - res_ty.fmt(self.sema.pt), - field_name.fmt(ip), - }); - }; - - const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index); + const value = try self.sema.pt.enumValueFieldIndex(res_ty, field_index); - return value.toIntern(); + return value.toIntern(); + }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + } } -fn lowerEnumLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const main_tokens = self.file.tree.nodes.items(.main_token); - const tags = self.file.tree.nodes.items(.tag); +fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; - - if (tags[node] != .enum_literal) { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + switch (node.get(self.file.zoir.?)) { + .enum_literal => |field_name| { + const field_name_interned = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + field_name.get(self.file.zoir.?), + .no_embedded_nulls, + ); + return ip.get(gpa, self.sema.pt.tid, .{ .enum_literal = field_name_interned }); + }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), } - - return ip.get(gpa, self.sema.pt.tid, .{ - .enum_literal = try self.identAsNullTerminatedString(main_tokens[node]), - }); } -fn lowerStructOrTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +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 => self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => unreachable, }; } -fn lowerTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerTuple(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 tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; - var buf: [2]Ast.Node.Index = undefined; - const elem_nodes = try self.elements(res_ty, &buf, node); + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; const field_types = tuple_info.types.get(ip); if (elem_nodes.len < field_types.len) { - return self.fail(.{ .node_abs = node }, "missing tuple field with index {}", .{elem_nodes.len}); + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "missing tuple field with index {}", + .{elem_nodes.len}, + ); } else if (elem_nodes.len > field_types.len) { - return self.fail(.{ .node_abs = node }, "index {} outside tuple of length {}", .{ - field_types.len, - elem_nodes[field_types.len], - }); + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "index {} outside tuple of length {}", + .{ + field_types.len, + elem_nodes.at(@intCast(field_types.len)), + }, + ); } const elems = try gpa.alloc(InternPool.Index, field_types.len); defer gpa.free(elems); - for (elems, elem_nodes, field_types) |*elem, elem_node, field_type| { - elem.* = try self.lowerExpr(elem_node, Type.fromInterned(field_type)); + for (0..elem_nodes.len) |i| { + elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), Type.fromInterned(field_types[i])); } return self.sema.pt.intern(.{ .aggregate = .{ @@ -698,15 +657,22 @@ fn lowerTuple(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In } }); } -fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { +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.resolveFully(self.sema.pt); const struct_info = self.sema.pt.zcu.typeToStruct(res_ty).?; - var buf: [2]Ast.Node.Index = undefined; - const field_nodes = try self.fields(res_ty, &buf, node); + const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.file.zoir.?)) { + .struct_literal => |fields| fields, + .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; const field_values = try gpa.alloc(InternPool.Index, struct_info.field_names.len); defer gpa.free(field_values); @@ -716,13 +682,20 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I field_values[i] = if (i < field_defaults.len) field_defaults[i] else .none; } - for (field_nodes) |field_node| { - const field_name_token = self.file.tree.firstToken(field_node) - 2; - const field_name = try self.identAsNullTerminatedString(field_name_token); + 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 field_node_ast = field_node.getAstNode(self.file.zoir.?); + const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; const name_index = struct_info.nameIndex(ip, field_name) orelse { return self.fail( - .{ .node_abs = field_node }, + .{ .node_abs = field_node.getAstNode(self.file.zoir.?) }, "unexpected field '{}'", .{field_name.fmt(ip)}, ); @@ -736,13 +709,14 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I .{field_name.fmt(ip)}, ); } + field_values[name_index] = try self.lowerExpr(field_node, field_type); } const field_names = struct_info.field_names.get(ip); for (field_values, field_names) |*value, name| { if (value.* == .none) return self.fail( - .{ .node_abs = node }, + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, "missing field {}", .{name.fmt(ip)}, ); @@ -753,8 +727,7 @@ fn lowerStruct(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.I } } }); } -fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const tags = self.file.tree.nodes.items(.tag); +fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; @@ -762,8 +735,8 @@ fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. if (ptr_info.flags.size != .Slice) { return self.fail( - .{ .node_abs = node }, - "ZON import cannot be coerced to non slice pointer", + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "non slice pointers are not available in ZON", .{}, ); } @@ -772,20 +745,51 @@ fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. 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) { - if (tags[node] == .string_literal or tags[node] == .multiline_string_literal) { - return self.lowerStringLiteral(node, res_ty); + switch (node.get(self.file.zoir.?)) { + .string_literal => |val| { + const string = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls); + const array_ty = try self.sema.pt.intern(.{ .array_type = .{ + .len = val.len, + .sentinel = .zero_u8, + .child = .u8_type, + } }); + const array_val = try self.sema.pt.intern(.{ .aggregate = .{ + .ty = array_ty, + .storage = .{ .bytes = string }, + } }); + return self.sema.pt.intern(.{ .slice = .{ + .ty = res_ty.toIntern(), + .ptr = try self.sema.pt.intern(.{ .ptr = .{ + .ty = .manyptr_const_u8_sentinel_0_type, + .base_addr = .{ .uav = .{ + .orig_ty = .slice_const_u8_sentinel_0_type, + .val = array_val, + } }, + .byte_offset = 0, + } }), + .len = (try self.sema.pt.intValue(Type.usize, val.len)).toIntern(), + } }); + }, + else => {}, } } // Slice literals - var buf: [2]NodeIndex = undefined; - const elem_nodes = try self.elements(res_ty, &buf, node); + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); defer gpa.free(elems); - for (elem_nodes, 0..) |elem_node, i| { - elems[i] = try self.lowerExpr(elem_node, Type.fromInterned(ptr_info.child)); + for (0..elem_nodes.len) |i| { + elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), Type.fromInterned(ptr_info.child)); } if (ptr_info.sentinel != .none) { @@ -836,90 +840,59 @@ fn lowerPointer(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool. } }); } -fn lowerStringLiteral(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const gpa = self.sema.gpa; +fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; - const main_tokens = self.file.tree.nodes.items(.main_token); - const tags = self.file.tree.nodes.items(.tag); - const data = self.file.tree.nodes.items(.data); - - const token = main_tokens[node]; - const raw_string = self.file.tree.tokenSlice(token); - - var bytes = std.ArrayListUnmanaged(u8){}; - defer bytes.deinit(gpa); - switch (tags[node]) { - .string_literal => switch (try std.zig.string_literal.parseWrite(bytes.writer(gpa), raw_string)) { - .success => {}, - .failure => |err| { - const offset = self.file.tree.tokens.items(.start)[token]; - return self.fail( - .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, - "{}", - .{err.fmtWithSource(raw_string)}, - ); - }, - }, - .multiline_string_literal => { - var parser = std.zig.string_literal.multilineParser(bytes.writer(gpa)); - var tok_i = data[node].lhs; - while (tok_i <= data[node].rhs) : (tok_i += 1) { - try parser.line(self.file.tree.tokenSlice(tok_i)); - } - }, - else => unreachable, - } - - const string = try ip.getOrPutString(gpa, self.sema.pt.tid, bytes.items, .maybe_embedded_nulls); - const array_ty = try self.sema.pt.intern(.{ .array_type = .{ - .len = bytes.items.len, - .sentinel = .zero_u8, - .child = .u8_type, - } }); - const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = string }, - } }); - return self.sema.pt.intern(.{ .slice = .{ - .ty = res_ty.toIntern(), - .ptr = try self.sema.pt.intern(.{ .ptr = .{ - .ty = .manyptr_const_u8_sentinel_0_type, - .base_addr = .{ .uav = .{ - .orig_ty = .slice_const_u8_sentinel_0_type, - .val = array_val, - } }, - .byte_offset = 0, - } }), - .len = (try self.sema.pt.intValue(Type.usize, bytes.items.len)).toIntern(), - } }); -} - -fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.Index { - const tags = self.file.tree.nodes.items(.tag); - const ip = &self.sema.pt.zcu.intern_pool; - const main_tokens = self.file.tree.nodes.items(.main_token); - try res_ty.resolveFully(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 = if (tags[node] == .enum_literal) b: { - const field_name = try self.identAsNullTerminatedString(main_tokens[node]); - break :b .{ field_name, null }; - } else b: { - var buf: [2]Ast.Node.Index = undefined; - const field_nodes = try self.fields(res_ty, &buf, node); - if (field_nodes.len > 1) { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); - } - const field_node = field_nodes[0]; - const field_name_token = self.file.tree.firstToken(field_node) - 2; - const field_name = try self.identAsNullTerminatedString(field_name_token); - break :b .{ field_name, field_node }; + 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: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.file.zoir.?)) { + .struct_literal => |fields| fields, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), + }; + if (fields.names.len != 1) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + } + const field_name = try ip.getOrPutString( + self.sema.gpa, + self.sema.pt.tid, + fields.names[0].get(self.file.zoir.?), + .no_embedded_nulls, + ); + break :b .{ field_name, fields.vals.at(0) }; + }, + else => return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ), }; const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); }; const tag_int = if (enum_tag_info.values.len == 0) b: { // Auto numbered fields @@ -940,7 +913,12 @@ fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{field_type.fmt(self.sema.pt)}); + return self.failWithNote( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{field_type.fmt(self.sema.pt)}, + "void is not available in ZON, but void union fields can be expressed as enum literals", + ); } break :b .void_value; }; @@ -951,77 +929,6 @@ fn lowerUnion(self: LowerZon, node: Ast.Node.Index, res_ty: Type) !InternPool.In }); } -fn fields( - self: LowerZon, - container: Type, - buf: *[2]NodeIndex, - node: NodeIndex, -) ![]const NodeIndex { - if (self.file.tree.fullStructInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.fail( - .{ .node_abs = init.ast.type_expr }, - "ZON cannot contain type expressions", - .{}, - ); - } - return init.ast.fields; - } - - if (self.file.tree.fullArrayInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.fail( - .{ .node_abs = init.ast.type_expr }, - "ZON cannot contain type expressions", - .{}, - ); - } - if (init.ast.elements.len != 0) { - return self.fail( - .{ .node_abs = node }, - "expected type '{}'", - .{container.fmt(self.sema.pt)}, - ); - } - return init.ast.elements; - } - - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{container.fmt(self.sema.pt)}); -} - -fn elements( - self: LowerZon, - container: Type, - buf: *[2]NodeIndex, - node: NodeIndex, -) ![]const NodeIndex { - if (self.file.tree.fullArrayInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.fail( - .{ .node_abs = init.ast.type_expr }, - "ZON cannot contain type expressions", - .{}, - ); - } - return init.ast.elements; - } - - if (self.file.tree.fullStructInit(buf, node)) |init| { - if (init.ast.type_expr != 0) { - return self.fail( - .{ .node_abs = init.ast.type_expr }, - "ZON cannot contain type expressions", - .{}, - ); - } - if (init.ast.fields.len == 0) { - return init.ast.fields; - } - } - - return self.fail(.{ .node_abs = node }, "expected type '{}'", .{container.fmt(self.sema.pt)}); -} - fn createErrorWithOptionalNote( self: LowerZon, src_loc: LazySrcLoc, @@ -1049,21 +956,3 @@ fn createErrorWithOptionalNote( err_msg.*.notes = notes; return err_msg; } - -fn failWithNumberError( - self: LowerZon, - token: Ast.TokenIndex, - err: NumberLiteralError, -) (Allocator.Error || error{AnalysisFail}) { - const offset = self.file.tree.tokens.items(.start)[token]; - const src_loc = try self.lazySrcLoc(.{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }); - const token_bytes = self.file.tree.tokenSlice(token); - const err_msg = try self.createErrorWithOptionalNote( - src_loc, - "{}", - .{err.fmtWithSource(token_bytes)}, - err.noteWithSource(token_bytes), - ); - try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); - return error.AnalysisFail; -} diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig index fc1b94639f60..78e4318733f5 100644 --- a/test/cases/compile_errors/@import_zon_coerce_pointer.zig +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -8,5 +8,5 @@ pub fn main() void { // output_mode=Exe // imports=zon/array.zon // -// array.zon:1:2: error: ZON import cannot be coerced to non slice pointer +// array.zon:1:2: error: non slice pointers are not available in ZON // tmp.zig:2:47: note: imported here From 2b576a1840f90bad87c49fb45ed57e3b1df42f2c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 21:18:06 -0800 Subject: [PATCH 32/51] Cleans up diff with master, resovles some feedback --- lib/std/fmt.zig | 2 +- lib/std/zig/Ast.zig | 3 +- lib/std/zig/AstGen.zig | 90 ++++--- lib/std/zig/ZonGen.zig | 16 +- lib/std/zig/number_literal.zig | 70 ------ lib/std/zig/string_literal.zig | 220 +----------------- lib/std/zon.zig | 41 ++-- lib/std/zon/parse.zig | 80 ++++--- src/Air.zig | 5 - src/Package/Manifest.zig | 98 +++++++- src/Zcu.zig | 2 +- src/zon.zig | 26 +-- test/behavior/zon.zig | 8 +- test/behavior/zon/escaped_struct.zon | 2 +- .../{slice-1.zon => slice1_no_newline.zon} | 0 .../@import_zon_doc_comment.zig | 11 + .../@import_zon_expected_void.zig | 13 ++ .../compile_errors/@import_zon_neg_char.zig | 11 + .../compile_errors/@import_zon_neg_nan.zig | 11 + .../compile_errors/@import_zon_no_rt.zig | 11 + .../compile_errors/@import_zon_oob_char_0.zig | 18 ++ .../compile_errors/@import_zon_oob_char_1.zig | 18 ++ .../compile_errors/@import_zon_oob_int_0.zig | 18 ++ .../compile_errors/@import_zon_oob_int_1.zig | 18 ++ .../compile_errors/@import_zon_oob_int_2.zig | 18 ++ .../compile_errors/@import_zon_oob_int_3.zig | 12 + .../cases/compile_errors/@import_zon_void.zig | 13 ++ test/cases/compile_errors/zon/char_32.zon | 1 + test/cases/compile_errors/zon/doc_comment.zon | 2 + test/cases/compile_errors/zon/int_32.zon | 1 + test/cases/compile_errors/zon/int_neg_33.zon | 1 + test/cases/compile_errors/zon/neg_char.zon | 1 + test/cases/compile_errors/zon/neg_nan.zon | 1 + .../cases/compile_errors/zon/simple_union.zon | 1 + test/cases/compile_errors/zon/void.zon | 1 + 35 files changed, 417 insertions(+), 427 deletions(-) rename test/behavior/zon/{slice-1.zon => slice1_no_newline.zon} (100%) create mode 100644 test/cases/compile_errors/@import_zon_doc_comment.zig create mode 100644 test/cases/compile_errors/@import_zon_expected_void.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_no_rt.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_void.zig create mode 100644 test/cases/compile_errors/zon/char_32.zon create mode 100644 test/cases/compile_errors/zon/doc_comment.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/neg_char.zon create mode 100644 test/cases/compile_errors/zon/neg_nan.zon create mode 100644 test/cases/compile_errors/zon/simple_union.zon create mode 100644 test/cases/compile_errors/zon/void.zon diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 078b40548dba..e6bf94fdf4ed 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1582,7 +1582,7 @@ test parseInt { } /// Like `parseIntWithGenericCharacter`, but with a sign argument. -pub fn parseIntWithSign( +fn parseIntWithSign( comptime Result: type, comptime Character: type, buf: []const Character, diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index d9050e9659f1..34531ccc3ede 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -7,13 +7,12 @@ /// Reference to externally-owned data. source: [:0]const u8, -mode: Mode, - tokens: TokenList.Slice, /// The root AST node is assumed to be index 0. Since there can be no /// references to the root node, this means 0 is available to indicate null. nodes: NodeList.Slice, extra_data: []Node.Index, +mode: Mode, errors: []const Error, diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 2baeaed896c0..e6a4f0d77d5a 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8888,22 +8888,36 @@ fn numberLiteral(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index, source_node: } } -fn failWithNumberError( - astgen: *AstGen, - err: std.zig.number_literal.Error, - token: Ast.TokenIndex, - bytes: []const u8, -) InnerError { - const note = err.noteWithSource(bytes); - const notes: []const u32 = if (note) |n| &.{try astgen.errNoteTok(token, "{s}", .{n})} else &.{}; - try astgen.appendErrorTokNotesOff( - token, - @as(u32, @intCast(err.offset())), - "{}", - .{err.fmtWithSource(bytes)}, - notes, - ); - return error.AnalysisFail; +fn failWithNumberError(astgen: *AstGen, err: std.zig.number_literal.Error, token: Ast.TokenIndex, bytes: []const u8) InnerError { + const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null; + switch (err) { + .leading_zero => if (is_float) { + return astgen.failTok(token, "number '{s}' has leading zero", .{bytes}); + } else { + return astgen.failTokNotes(token, "number '{s}' has leading zero", .{bytes}, &.{ + try astgen.errNoteTok(token, "use '0o' prefix for octal literals", .{}), + }); + }, + .digit_after_base => return astgen.failTok(token, "expected a digit after base prefix", .{}), + .upper_case_base => |i| return astgen.failOff(token, @intCast(i), "base prefix must be lowercase", .{}), + .invalid_float_base => |i| return astgen.failOff(token, @intCast(i), "invalid base for float literal", .{}), + .repeated_underscore => |i| return astgen.failOff(token, @intCast(i), "repeated digit separator", .{}), + .invalid_underscore_after_special => |i| return astgen.failOff(token, @intCast(i), "expected digit before digit separator", .{}), + .invalid_digit => |info| return astgen.failOff(token, @intCast(info.i), "invalid digit '{c}' for {s} base", .{ bytes[info.i], @tagName(info.base) }), + .invalid_digit_exponent => |i| return astgen.failOff(token, @intCast(i), "invalid digit '{c}' in exponent", .{bytes[i]}), + .duplicate_exponent => |i| return astgen.failOff(token, @intCast(i), "duplicate exponent", .{}), + .exponent_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before exponent", .{}), + .special_after_underscore => |i| return astgen.failOff(token, @intCast(i), "expected digit before '{c}'", .{bytes[i]}), + .trailing_special => |i| return astgen.failOff(token, @intCast(i), "expected digit after '{c}'", .{bytes[i - 1]}), + .trailing_underscore => |i| return astgen.failOff(token, @intCast(i), "trailing digit separator", .{}), + .duplicate_period => unreachable, // Validated by tokenizer + .invalid_character => unreachable, // Validated by tokenizer + .invalid_exponent_sign => |i| { + assert(bytes.len >= 2 and bytes[0] == '0' and bytes[1] == 'x'); // Validated by tokenizer + return astgen.failOff(token, @intCast(i), "sign '{c}' cannot follow digit '{c}' in hex base", .{ bytes[i], bytes[i - 1] }); + }, + .period_after_exponent => |i| return astgen.failOff(token, @intCast(i), "unexpected period after exponent", .{}), + } } fn asmExpr( @@ -11504,20 +11518,9 @@ 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 astgen.failOff( - token, - offset + @as(u32, @intCast(err.offset())), - "{}", - .{err.fmtWithSource(raw_string)}, - ); + return err.lower(raw_string, offset, AstGen.failOff, .{ astgen, token }); } fn failNode( @@ -11635,7 +11638,7 @@ fn appendErrorTokNotesOff( comptime format: []const u8, args: anytype, notes: []const u32, -) Allocator.Error!void { +) !void { @branchHint(.cold); const gpa = astgen.gpa; const string_bytes = &astgen.string_bytes; @@ -11764,17 +11767,32 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !IndexSlice { } fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice { + const tree = astgen.tree; + const node_datas = tree.nodes.items(.data); + + const start = node_datas[node].lhs; + const end = node_datas[node].rhs; + const gpa = astgen.gpa; - const data = astgen.tree.nodes.items(.data); const string_bytes = &astgen.string_bytes; const str_index = string_bytes.items.len; - var parser = std.zig.string_literal.multilineParser(string_bytes.writer(gpa)); - var tok_i = data[node].lhs; - while (tok_i <= data[node].rhs) : (tok_i += 1) { - try parser.line(astgen.tree.tokenSlice(tok_i)); + // First line: do not append a newline. + var tok_i = start; + { + const slice = tree.tokenSlice(tok_i); + const line_bytes = slice[2..]; + try string_bytes.appendSlice(gpa, line_bytes); + tok_i += 1; + } + // Following lines: each line prepends a newline. + while (tok_i <= end) : (tok_i += 1) { + const slice = tree.tokenSlice(tok_i); + const line_bytes = slice[2..]; + try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1); + string_bytes.appendAssumeCapacity('\n'); + string_bytes.appendSliceAssumeCapacity(line_bytes); } - const len = string_bytes.items.len - str_index; try string_bytes.append(gpa, 0); return IndexSlice{ diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 09467da51ca0..6eab3b563512 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -250,7 +250,21 @@ 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", .{}), + try zg.errNoteNode(node, "for example, `.{{ .foo = {{}} }}` becomes `.foo`", .{}), + }); + } else { + try zg.addErrorNode(node, "blocks are not allowed in ZON", .{}); + } + }, .array_init_one, .array_init_one_comma, diff --git a/lib/std/zig/number_literal.zig b/lib/std/zig/number_literal.zig index 40b8c44c176d..5ff027a43f67 100644 --- a/lib/std/zig/number_literal.zig +++ b/lib/std/zig/number_literal.zig @@ -58,40 +58,6 @@ pub const Error = union(enum) { invalid_exponent_sign: usize, /// Period comes directly after exponent. period_after_exponent: usize, - - pub fn fmtWithSource(self: Error, bytes: []const u8) std.fmt.Formatter(formatErrorWithSource) { - return .{ .data = .{ .err = self, .bytes = bytes } }; - } - - pub fn noteWithSource(self: Error, bytes: []const u8) ?[]const u8 { - if (self == .leading_zero) { - const is_float = std.mem.indexOfScalar(u8, bytes, '.') != null; - if (!is_float) return "use '0o' prefix for octal literals"; - } - return null; - } - - pub fn offset(self: Error) usize { - return switch (self) { - .leading_zero => 0, - .digit_after_base => 0, - .upper_case_base => |i| i, - .invalid_float_base => |i| i, - .repeated_underscore => |i| i, - .invalid_underscore_after_special => |i| i, - .invalid_digit => |e| e.i, - .invalid_digit_exponent => |i| i, - .duplicate_period => 0, - .duplicate_exponent => |i| i, - .exponent_after_underscore => |i| i, - .special_after_underscore => |i| i, - .trailing_special => |i| i, - .trailing_underscore => |i| i, - .invalid_character => |i| i, - .invalid_exponent_sign => |i| i, - .period_after_exponent => |i| i, - }; - } }; const FormatWithSource = struct { @@ -99,42 +65,6 @@ const FormatWithSource = struct { err: Error, }; -fn formatErrorWithSource( - self: FormatWithSource, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, -) !void { - _ = options; - _ = fmt; - switch (self.err) { - .leading_zero => try writer.print("number '{s}' has leading zero", .{self.bytes}), - .digit_after_base => try writer.writeAll("expected a digit after base prefix"), - .upper_case_base => try writer.writeAll("base prefix must be lowercase"), - .invalid_float_base => try writer.writeAll("invalid base for float literal"), - .repeated_underscore => try writer.writeAll("repeated digit separator"), - .invalid_underscore_after_special => try writer.writeAll("expected digit before digit separator"), - .invalid_digit => |info| try writer.print("invalid digit '{c}' for {s} base", .{ self.bytes[info.i], @tagName(info.base) }), - .invalid_digit_exponent => |i| try writer.print("invalid digit '{c}' in exponent", .{self.bytes[i]}), - .duplicate_exponent => try writer.writeAll("duplicate exponent"), - .exponent_after_underscore => try writer.writeAll("expected digit before exponent"), - .special_after_underscore => |i| try writer.print("expected digit before '{c}'", .{self.bytes[i]}), - .trailing_special => |i| try writer.print("expected digit after '{c}'", .{self.bytes[i - 1]}), - .trailing_underscore => try writer.writeAll("trailing digit separator"), - .duplicate_period => try writer.writeAll("duplicate period"), - .invalid_character => try writer.writeAll("invalid character"), - .invalid_exponent_sign => |i| { - const hex = self.bytes.len >= 2 and self.bytes[0] == '0' and self.bytes[1] == 'x'; - if (hex) { - try writer.print("sign '{c}' cannot follow digit '{c}' in hex base", .{ self.bytes[i], self.bytes[i - 1] }); - } else { - try writer.print("sign '{c}' cannot follow digit '{c}' in current base", .{ self.bytes[i], self.bytes[i - 1] }); - } - }, - .period_after_exponent => try writer.writeAll("unexpected period after exponent"), - } -} - /// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString. /// Valid for any input. pub fn parseNumberLiteral(bytes: []const u8) Result { diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index daba9b147f39..716a9b90f01c 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -43,7 +43,7 @@ pub const Error = union(enum) { pub fn lower( err: Error, raw_string: []const u8, - off: u32, + offset: u32, comptime func: anytype, first_args: anytype, ) @typeInfo(@TypeOf(func)).@"fn".return_type.? { @@ -66,81 +66,15 @@ pub const Error = union(enum) { .empty_char_literal => .{ "empty character literal", .{} }, }; return @call(.auto, func, first_args ++ .{ - off + bad_index, + offset + bad_index, fmt_str, args, }); }, } } - - pub fn fmtWithSource(self: Error, raw_string: []const u8) std.fmt.Formatter(formatErrorWithSource) { - return .{ .data = .{ .err = self, .raw_string = raw_string } }; - } - - pub fn offset(self: Error) usize { - return switch (self) { - .invalid_escape_character => |i| i, - .expected_hex_digit => |i| i, - .empty_unicode_escape_sequence => |i| i, - .expected_hex_digit_or_rbrace => |i| i, - .invalid_unicode_codepoint => |i| i, - .expected_lbrace => |i| i, - .expected_rbrace => |i| i, - .expected_single_quote => |i| i, - .invalid_character => |i| i, - .empty_char_literal => 0, - }; - } }; -const FormatWithSource = struct { - raw_string: []const u8, - err: Error, -}; - -fn formatErrorWithSource( - self: FormatWithSource, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, -) !void { - _ = options; - _ = fmt; - 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.print("empty character literal", .{}); - }, - } -} - /// Asserts the slice starts and ends with single-quotes. /// Returns an error if there is not exactly one UTF-8 codepoint in between. pub fn parseCharLiteral(slice: []const u8) ParsedCharLiteral { @@ -348,7 +282,7 @@ test parseCharLiteral { /// Parses `bytes` as a Zig string literal and writes the result to the std.io.Writer type. /// Asserts `bytes` has '"' at beginning and end. -pub fn parseWrite(writer: anytype, bytes: []const u8) !Result { +pub fn parseWrite(writer: anytype, bytes: []const u8) error{OutOfMemory}!Result { assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"'); var index: usize = 1; @@ -413,151 +347,3 @@ test parseAlloc { try expect(eql(u8, "foo", try parseAlloc(alloc, "\"f\x6f\x6f\""))); try expect(eql(u8, "f💯", try parseAlloc(alloc, "\"f\u{1f4af}\""))); } - -/// Parses one line at a time of a multiline Zig string literal to a std.io.Writer type. Does not append a null terminator. -pub fn MultilineParser(comptime Writer: type) type { - return struct { - writer: Writer, - first_line: bool, - - pub fn init(writer: Writer) @This() { - return .{ - .writer = writer, - .first_line = true, - }; - } - - /// Parse one line of a multiline string, writing the result to the writer prepending a - /// newline if necessary. - /// - /// Asserts bytes begins with "\\". The line may be terminated with '\n' or "\r\n", but may - /// not contain any interior newlines. - pub fn line(self: *@This(), bytes: []const u8) Writer.Error!void { - assert(bytes.len >= 2 and bytes[0] == '\\' and bytes[1] == '\\'); - var terminator_len: usize = 0; - terminator_len += @intFromBool(bytes[bytes.len - 1] == '\n'); - terminator_len += @intFromBool(bytes[bytes.len - 2] == '\r'); - if (self.first_line) { - self.first_line = false; - } else { - try self.writer.writeByte('\n'); - } - try self.writer.writeAll(bytes[2 .. bytes.len - terminator_len]); - } - }; -} - -pub fn multilineParser(writer: anytype) MultilineParser(@TypeOf(writer)) { - return MultilineParser(@TypeOf(writer)).init(writer); -} - -test "parse multiline" { - // Varying newlines - { - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo"); - try std.testing.expectEqualStrings("foo", parsed.items); - try parser.line("\\\\bar"); - try std.testing.expectEqualStrings("foo\nbar", parsed.items); - } - - { - const temp = - \\foo - \\bar - ; - try std.testing.expectEqualStrings("foo\nbar", temp); - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo"); - try std.testing.expectEqualStrings("foo", parsed.items); - // XXX: this adds the newline but like...does the input ever actually have a newline there? - try parser.line("\\\\bar\n"); - try std.testing.expectEqualStrings("foo\nbar", parsed.items); - } - - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo"); - try std.testing.expectEqualStrings("foo", parsed.items); - try parser.line("\\\\bar\r\n"); - try std.testing.expectEqualStrings("foo\nbar", parsed.items); - } - - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo\n"); - try std.testing.expectEqualStrings("foo", parsed.items); - try parser.line("\\\\bar"); - try std.testing.expectEqualStrings("foo\nbar", parsed.items); - } - - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo\r\n"); - try std.testing.expectEqualStrings("foo", parsed.items); - try parser.line("\\\\bar"); - try std.testing.expectEqualStrings("foo\nbar", parsed.items); - } - } - - // Empty lines - { - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\"); - try std.testing.expectEqualStrings("", parsed.items); - try parser.line("\\\\"); - try std.testing.expectEqualStrings("\n", parsed.items); - try parser.line("\\\\foo"); - try std.testing.expectEqualStrings("\n\nfoo", parsed.items); - try parser.line("\\\\bar"); - try std.testing.expectEqualStrings("\n\nfoo\nbar", parsed.items); - } - - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\foo"); - try std.testing.expectEqualStrings("foo", parsed.items); - try parser.line("\\\\"); - try std.testing.expectEqualStrings("foo\n", parsed.items); - try parser.line("\\\\bar"); - try std.testing.expectEqualStrings("foo\n\nbar", parsed.items); - try parser.line("\\\\"); - try std.testing.expectEqualStrings("foo\n\nbar\n", parsed.items); - } - } - - // No escapes - { - var parsed = std.ArrayList(u8).init(std.testing.allocator); - defer parsed.deinit(); - const writer = parsed.writer(); - var parser = multilineParser(writer); - try parser.line("\\\\no \\n escape"); - try std.testing.expectEqualStrings("no \\n escape", parsed.items); - try parser.line("\\\\still no \\n escape"); - try std.testing.expectEqualStrings("no \\n escape\nstill no \\n escape", parsed.items); - } -} diff --git a/lib/std/zon.zig b/lib/std/zon.zig index 232229eb3df8..4288c52729ab 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -9,17 +9,13 @@ //! * number literals (including `nan` and `inf`) //! * character literals //! * enum literals -//! * `null` and `void` literals +//! * `null` literals //! * string literals //! * multiline string literals //! -//! Supported Zig containers: +//! Supported Zig container types: //! * anonymous struct literals //! * anonymous tuple literals -//! * slices -//! * notated as a reference to a tuple literal -//! * this syntax will likely be removed in the future, at which point ZON will not distinguish -//! between slices and tuples //! //! Here is an example ZON object: //! ```zon @@ -27,7 +23,7 @@ //! .a = 1.5, //! .b = "hello, world!", //! .c = .{ true, false }, -//! .d = &.{ 1, 2, 3 }, +//! .d = .{ 1, 2, 3 }, //! } //! ``` //! @@ -36,25 +32,22 @@ //! "This string is a valid ZON object." //! ``` //! -//! \* ZON is not currently a true subset of Zig, because it supports `nan` and +//! * ZON is not currently a true subset of Zig, because it supports `nan` and //! `inf` literals, which Zig does not. //! //! # Deserialization //! -//! The simplest way to deserialize ZON at runtime is `parseFromSlice`. (For reading ZON at +//! The simplest way to deserialize ZON at runtime is `parseFromSlice`. (For parsing ZON at //! comptime, you can use `@import`.) //! -//! If you need lower level control, or more detailed diagnostics, you can generate the AST yourself -//! with `std.zig.Ast.parse` and then deserialize it with: -//! * `parseFromAst` -//! * `parseFromAstNoAlloc` -//! -//! If you'd like to deserialize just part of an AST, you can use: -//! * `parseFromAstNode` -//! * `parseFromAstNodeNoAlloc` +//! Parsing from individual Zoir nodes is also available: +//! * `parseFromZoir` +//! * `parseFromZoirNode` +//! * `parseFromZoirNode` +//! * `parseFromZoirNodeNoAlloc` //! //! If you need lower level control than provided by this module, you can operate directly on the -//! results of `std.zig.Ast.parse`. +//! results of `std.zig.Zoir` directly. This module is a good example of how that can be done. //! //! //! # Serialization @@ -72,13 +65,13 @@ //! Note that serializing floats with more than 64 bits may result in a loss of precision //! (see https://github.com/ziglang/zig/issues/1181). -pub const ParseOptions = @import("zon/parse.zig").ParseOptions; -pub const ParseStatus = @import("zon/parse.zig").ParseStatus; +pub const ParseOptions = @import("zon/parse.zig").Options; +pub const ParseStatus = @import("zon/parse.zig").Status; pub const parseFromSlice = @import("zon/parse.zig").parseFromSlice; -pub const parseFromAst = @import("zon/parse.zig").parseFromAst; -pub const parseFromAstNoAlloc = @import("zon/parse.zig").parseFromAstNoAlloc; -pub const parseFromAstNode = @import("zon/parse.zig").parseFromAstNode; -pub const parseFromAstNodeNoAlloc = @import("zon/parse.zig").parseFromAstNodeNoAlloc; +pub const parseFromZoir = @import("zon/parse.zig").parseFromZoir; +pub const parseFromZoirNoAlloc = @import("zon/parse.zig").parseFromZoirNoAlloc; +pub const parseFromZoirNode = @import("zon/parse.zig").parseFromZoirNode; +pub const parseFromZoirNodeNoAlloc = @import("zon/parse.zig").parseFromZoirNodeNoAlloc; pub const parseFree = @import("zon/parse.zig").parseFree; pub const StringifierOptions = @import("zon/stringify.zig").StringifierOptions; diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 74a9f821c88b..60e9402673ef 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -16,7 +16,7 @@ zoir: Zoir, status: ?*Status, /// Configuration for the runtime parser. -pub const ParseOptions = struct { +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 @@ -258,7 +258,7 @@ pub fn parseFromSlice( gpa: Allocator, source: [:0]const u8, status: ?*Status, - comptime options: ParseOptions, + comptime options: Options, ) error{ OutOfMemory, ParseZon }!T { if (status) |s| s.assertEmpty(); @@ -286,7 +286,7 @@ pub fn parseFromZoir( ast: Ast, zoir: Zoir, status: ?*Status, - comptime options: ParseOptions, + comptime options: Options, ) error{ OutOfMemory, ParseZon }!T { return parseFromZoirNode(T, gpa, ast, zoir, .root, status, options); } @@ -299,7 +299,7 @@ pub fn parseFromZoirNoAlloc( ast: Ast, zoir: Zoir, status: ?*Status, - comptime options: ParseOptions, + comptime options: Options, ) error{ParseZon}!T { return parseFromZoirNodeNoAlloc(T, ast, zoir, .root, status, options); } @@ -322,7 +322,7 @@ pub fn parseFromZoirNode( zoir: Zoir, node: Zoir.Node.Index, status: ?*Status, - comptime options: ParseOptions, + comptime options: Options, ) error{ OutOfMemory, ParseZon }!T { if (status) |s| { s.assertEmpty(); @@ -351,7 +351,7 @@ pub fn parseFromZoirNodeNoAlloc( zoir: Zoir, node: Zoir.Node.Index, status: ?*Status, - comptime options: ParseOptions, + comptime options: Options, ) error{ParseZon}!T { if (comptime requiresAllocator(T)) { @compileError(@typeName(T) ++ ": requires allocator"); @@ -474,7 +474,7 @@ pub fn parseFree(gpa: Allocator, value: anytype) void { fn parseExpr( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { // Keep in sync with parseFree, stringify, and requiresAllocator. @@ -492,14 +492,14 @@ fn parseExpr( .@"union" => return self.parseUnion(T, options, node), .optional => return self.parseOptional(T, options, node), - else => @compileError(@typeName(T) ++ ": cannot parse this type"), + else => @compileError("type '" ++ @typeName(T) ++ "' is not available in ZON"), } } fn parseOptional( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { if (node.get(self.zoir) == .null) { @@ -533,7 +533,7 @@ test "std.zon optional" { fn parseUnion( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { const @"union" = @typeInfo(T).@"union"; @@ -594,10 +594,8 @@ fn parseUnion( switch (field_index) { inline 0...field_infos.len - 1 => |i| { if (field_infos[i].type == void) { - return self.failNode( - field_val, - "void union field not expressed as enum literal", - ); + // XXX: remove? + return self.failNode(field_val, "expected type 'void'"); } else { const value = try self.parseExpr(field_infos[i].type, options, field_val); return @unionInit(T, field_infos[i].name, value); @@ -660,7 +658,7 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x=1}", &status, .{})); - try std.testing.expectFmt("1:6: error: void union field not expressed as enum literal\n", "{}", .{status}); + try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status}); } // Extra field @@ -712,7 +710,7 @@ test "std.zon unions" { fn parseStruct( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.zoir)) { @@ -970,7 +968,7 @@ test "std.zon structs" { fn parseTuple( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { @@ -1072,7 +1070,7 @@ test "std.zon tuples" { fn parseArray( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { @@ -1241,7 +1239,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: expected bool\n", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } // Slice @@ -1249,7 +1247,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: expected bool\n", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } } @@ -1284,7 +1282,7 @@ test "std.zon arrays and slices" { fn parsePointer( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { switch (node.get(self.zoir)) { @@ -1325,7 +1323,7 @@ fn parseString( fn parseSlice( self: *@This(), comptime T: type, - comptime options: ParseOptions, + comptime options: Options, nodes: Zoir.Node.Index.Range, ) error{ OutOfMemory, ParseZon }!T { const pointer = @typeInfo(T).pointer; @@ -1492,7 +1490,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([]const u8, gpa, ".{false}", &status, .{}), ); - try std.testing.expectFmt("1:3: error: expected u8\n", "{}", .{status}); + try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{}", .{status}); } // Invalid string literal @@ -1672,7 +1670,7 @@ fn failNode(self: @This(), node: Zoir.Node.Index, message: []const u8) error{Par fn failCannotRepresent(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon} { @branchHint(.cold); - return self.failNode(node, @typeName(T) ++ " cannot represent value"); + return self.failNode(node, "type '" ++ @typeName(T) ++ "' cannot represent value"); } fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?usize) error{ParseZon} { @@ -1760,7 +1758,7 @@ fn parseBool(self: @This(), node: Zoir.Node.Index) error{ParseZon}!bool { switch (node.get(self.zoir)) { .true => return true, .false => return false, - else => return self.failNode(node, "expected bool"), + else => return self.failNode(node, "expected type 'bool'"), } } @@ -1787,7 +1785,7 @@ test "std.zon parse bool" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(bool, gpa, "123", &status, .{})); - try std.testing.expectFmt("1:1: error: expected bool\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{}", .{status}); } } @@ -1808,7 +1806,7 @@ fn parseInt( .char_literal => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), - else => return self.failNode(node, "expected " ++ @typeName(T)), + else => return self.failNode(node, "expected type '" ++ @typeName(T) ++ "'"), } } @@ -1841,7 +1839,7 @@ fn parseFloat( .neg_inf => return -std.math.inf(T), .nan => return std.math.nan(T), .char_literal => |val| return @floatFromInt(val), - else => return self.failNode(node, "expected " ++ @typeName(T)), + else => return self.failNode(node, "expected type '" ++ @typeName(T) ++ "'"), } } @@ -1927,13 +1925,13 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "36893488147419103232", &status, .{})); - try std.testing.expectFmt("1:1: error: i66 cannot represent value\n", "{}", .{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, parseFromSlice(i66, gpa, "-36893488147419103233", &status, .{})); - try std.testing.expectFmt("1:1: error: i66 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'i66' cannot represent value\n", "{}", .{status}); } // Test parsing whole number floats as integers @@ -2026,7 +2024,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "true", &status, .{})); - try std.testing.expectFmt("1:1: error: expected u8\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{}", .{status}); } // Failing because an int is out of range @@ -2034,7 +2032,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "256", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); } // Failing because a negative int is out of range @@ -2042,7 +2040,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-129", &status, .{})); - try std.testing.expectFmt("1:1: error: i8 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'i8' cannot represent value\n", "{}", .{status}); } // Failing because an unsigned int is negative @@ -2050,7 +2048,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); } // Failing because a float is non-whole @@ -2058,7 +2056,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "1.5", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); } // Failing because a float is negative @@ -2066,7 +2064,7 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1.0", &status, .{})); - try std.testing.expectFmt("1:1: error: u8 cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); } // Negative integer zero @@ -2224,7 +2222,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // nan as int not allowed @@ -2232,7 +2230,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // inf as int not allowed @@ -2240,7 +2238,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "inf", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // -inf as int not allowed @@ -2248,7 +2246,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-inf", &status, .{})); - try std.testing.expectFmt("1:1: error: expected i8\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } // Bad identifier as float @@ -2276,7 +2274,7 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "\"foo\"", &status, .{})); - try std.testing.expectFmt("1:1: error: expected f32\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status}); } } diff --git a/src/Air.zig b/src/Air.zig index 883991488c7c..4589bb1557cf 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1020,11 +1020,6 @@ pub const Inst = struct { pub fn toType(ref: Ref) Type { return Type.fromInterned(ref.toInterned().?); } - - pub fn toTypeAllowNone(ref: Ref) ?Type { - if (ref == .none) return null; - return ref.toType(); - } }; /// All instructions have an 8-byte payload, which is contained within diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 7691ac3405e1..4eed6cc386e6 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -14,7 +14,6 @@ pub const Digest = [Hash.digest_length]u8; pub const multihash_len = 1 + 1 + Hash.digest_length; pub const multihash_hex_digest_len = 2 * multihash_len; pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; -const AstGen = std.zig.AstGen; pub const Dependency = struct { location: Location, @@ -457,6 +456,7 @@ const Parse = struct { return duped; } + /// TODO: try to DRY this with AstGen.parseStrLit fn parseStrLit( p: *Parse, token: Ast.TokenIndex, @@ -470,13 +470,95 @@ const Parse = struct { buf.* = buf_managed.moveToUnmanaged(); switch (try result) { .success => {}, - .failure => |err| try appendErrorOff( - p, - token, - offset + @as(u32, @intCast(err.offset())), - "{}", - err.fmtWithSource(raw_string), - ), + .failure => |err| try p.appendStrLitError(err, token, bytes, offset), + } + } + + /// TODO: try to DRY this with AstGen.failWithStrLitError + fn appendStrLitError( + p: *Parse, + err: std.zig.string_literal.Error, + token: Ast.TokenIndex, + bytes: []const u8, + offset: u32, + ) Allocator.Error!void { + const raw_string = bytes[offset..]; + switch (err) { + .invalid_escape_character => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "invalid escape character: '{c}'", + .{raw_string[bad_index]}, + ); + }, + .expected_hex_digit => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "expected hex digit, found '{c}'", + .{raw_string[bad_index]}, + ); + }, + .empty_unicode_escape_sequence => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "empty unicode escape sequence", + .{}, + ); + }, + .expected_hex_digit_or_rbrace => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "expected hex digit or '}}', found '{c}'", + .{raw_string[bad_index]}, + ); + }, + .invalid_unicode_codepoint => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "unicode escape does not correspond to a valid unicode scalar value", + .{}, + ); + }, + .expected_lbrace => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "expected '{{', found '{c}", + .{raw_string[bad_index]}, + ); + }, + .expected_rbrace => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "expected '}}', found '{c}", + .{raw_string[bad_index]}, + ); + }, + .expected_single_quote => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "expected single quote ('), found '{c}", + .{raw_string[bad_index]}, + ); + }, + .invalid_character => |bad_index| { + try p.appendErrorOff( + token, + offset + @as(u32, @intCast(bad_index)), + "invalid byte in string or character literal: '{c}'", + .{raw_string[bad_index]}, + ); + }, + .empty_char_literal => { + try p.appendErrorOff(token, offset, "empty character literal", .{}); + }, } } diff --git a/src/Zcu.zig b/src/Zcu.zig index d3264de47d35..7d45162c4758 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -677,7 +677,7 @@ pub const File = struct { /// successful, this field is unloaded. prev_zir: ?*Zir = null, - /// Whether the file is Zig or ZON. This filed is always populated. + /// Whether the file is Zig or ZON. This field is always populated. mode: Ast.Mode, pub const Status = enum { diff --git a/src/zon.zig b/src/zon.zig index 9cd839817849..6b15b91870bc 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -67,22 +67,10 @@ fn fail( loc: LazySrcLoc.Offset, comptime format: []const u8, args: anytype, -) (Allocator.Error || error{AnalysisFail}) { - @branchHint(.cold); - return self.failWithNote(loc, format, args, null); -} - -fn failWithNote( - self: LowerZon, - loc: LazySrcLoc.Offset, - comptime format: []const u8, - args: anytype, - note: ?[]const u8, ) (Allocator.Error || error{AnalysisFail}) { @branchHint(.cold); const src_loc = try self.lazySrcLoc(loc); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); - if (note) |n| try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "{s}", .{n}); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); return error.AnalysisFail; @@ -107,7 +95,7 @@ fn ident(self: LowerZon, token: Ast.TokenIndex) !Ident { const gpa = self.sema.gpa; const raw_string = bytes[1..bytes.len]; - var parsed = std.ArrayListUnmanaged(u8){}; + var parsed: std.ArrayListUnmanaged(u8) = .{}; defer parsed.deinit(gpa); switch (try std.zig.string_literal.parseWrite(parsed.writer(gpa), raw_string)) { @@ -910,14 +898,20 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I } }); const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); const val = if (maybe_field_node) |field_node| b: { + if (field_type.toIntern() == .void_type) { + return self.fail( + .{ .node_abs = field_node.getAstNode(self.file.zoir.?) }, + "expected type 'void'", + .{}, + ); + } break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.failWithNote( + return self.fail( .{ .node_abs = node.getAstNode(self.file.zoir.?) }, "expected type '{}'", - .{field_type.fmt(self.sema.pt)}, - "void is not available in ZON, but void union fields can be expressed as enum literals", + .{res_ty.fmt(self.sema.pt)}, ); } break :b .void_value; diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 2e68496b6d42..e00019a9f392 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -46,7 +46,7 @@ test "union" { z: void, }; - const union1: Union = @import("zon/union1.zon"); + const union1: Union = comptime @import("zon/union1.zon"); const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); @@ -144,16 +144,16 @@ test "slices, arrays, tuples" { { const expected_slice: []const u8 = &.{1}; - const found_slice: []const u8 = @import("zon/slice-1.zon"); + 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/slice-1.zon"); + 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/slice-1.zon"); + const found_tuple: T = @import("zon/slice1_no_newline.zon"); try expectEqual(expected_tuple, found_tuple); } diff --git a/test/behavior/zon/escaped_struct.zon b/test/behavior/zon/escaped_struct.zon index c5cb978f3303..f304aa632752 100644 --- a/test/behavior/zon/escaped_struct.zon +++ b/test/behavior/zon/escaped_struct.zon @@ -1,2 +1,2 @@ -// zig fmt: off + .{ .@"0" = 1.5, .@"foo" = 2 } diff --git a/test/behavior/zon/slice-1.zon b/test/behavior/zon/slice1_no_newline.zon similarity index 100% rename from test/behavior/zon/slice-1.zon rename to test/behavior/zon/slice1_no_newline.zon 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..00d6f0a8f1d7 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_doc_comment.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: struct { foo: type } = @import("zon/doc_comment.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_expected_void.zig b/test/cases/compile_errors/@import_zon_expected_void.zig new file mode 100644 index 000000000000..d6216ad60b15 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_expected_void.zig @@ -0,0 +1,13 @@ +pub fn main() void { + const U = union(enum) { a: void }; + const f: U = @import("zon/simple_union.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_neg_char.zig b/test/cases/compile_errors/@import_zon_neg_char.zig new file mode 100644 index 000000000000..2cf8b8b18d30 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_neg_char.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: u8 = @import("zon/neg_char.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..be623bf2b0c2 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_neg_nan.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f: u8 = @import("zon/neg_nan.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_no_rt.zig b/test/cases/compile_errors/@import_zon_no_rt.zig new file mode 100644 index 000000000000..32e0bc7ad8eb --- /dev/null +++ b/test/cases/compile_errors/@import_zon_no_rt.zig @@ -0,0 +1,11 @@ +pub fn main() void { + const f = @import("zon/simple_union.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/simple_union.zon +// +// tmp.zig:2:23: error: import ZON must have a known result type 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..31931dbda9e5 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_char_0.zig @@ -0,0 +1,18 @@ +pub fn main() void { + { + const f: u6 = @import("zon/char_32.zon"); + _ = f; + } + { + const f: u5 = @import("zon/char_32.zon"); + _ = f; + } +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..67c7866408bd --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_char_1.zig @@ -0,0 +1,18 @@ +pub fn main() void { + { + const f: i7 = @import("zon/char_32.zon"); + _ = f; + } + { + const f: i6 = @import("zon/char_32.zon"); + _ = f; + } +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..1ac2160512a3 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_int_0.zig @@ -0,0 +1,18 @@ +pub fn main() void { + { + const f: u6 = @import("zon/int_32.zon"); + _ = f; + } + { + const f: u5 = @import("zon/int_32.zon"); + _ = f; + } +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..75757b980df6 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_int_1.zig @@ -0,0 +1,18 @@ +pub fn main() void { + { + const f: i7 = @import("zon/int_32.zon"); + _ = f; + } + { + const f: i6 = @import("zon/int_32.zon"); + _ = f; + } +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..c1097da3cd29 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_int_2.zig @@ -0,0 +1,18 @@ +pub fn main() void { + { + const f: i7 = @import("zon/int_neg_33.zon"); + _ = f; + } + { + const f: i6 = @import("zon/int_neg_33.zon"); + _ = f; + } +} + +// error +// backend=stage2 +// output_mode=Exe +// 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..7240fc6033be --- /dev/null +++ b/test/cases/compile_errors/@import_zon_oob_int_3.zig @@ -0,0 +1,12 @@ +pub fn main() void { + const f: u64 = @import("zon/int_neg_33.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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_void.zig b/test/cases/compile_errors/@import_zon_void.zig new file mode 100644 index 000000000000..f09d6ccded01 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_void.zig @@ -0,0 +1,13 @@ +pub fn main() void { + const f: union { foo: void } = @import("zon/void.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// 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 +// void.zon:1:11: note: for example, `.{ .foo = {} }` becomes `.foo` 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/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/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/neg_char.zon b/test/cases/compile_errors/zon/neg_char.zon new file mode 100644 index 000000000000..b14b16f3d6e9 --- /dev/null +++ b/test/cases/compile_errors/zon/neg_char.zon @@ -0,0 +1 @@ +-'a' diff --git a/test/cases/compile_errors/zon/neg_nan.zon b/test/cases/compile_errors/zon/neg_nan.zon new file mode 100644 index 000000000000..c5c60a60cd2e --- /dev/null +++ b/test/cases/compile_errors/zon/neg_nan.zon @@ -0,0 +1 @@ +-nan diff --git a/test/cases/compile_errors/zon/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/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 = {} } From aec04e46ba8a1b85b608c6c300070d769a31b2db Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:15:46 -0800 Subject: [PATCH 33/51] Moves big int -> float conversion to standard library, uses --- lib/compiler/aro/aro/Value.zig | 2 +- lib/compiler/aro/backend/Interner.zig | 4 +-- lib/std/math/big/int.zig | 41 +++++++++++++++++++++++---- lib/std/zon/parse.zig | 18 ++---------- src/InternPool.zig | 18 ++++++------ src/Value.zig | 29 ++++--------------- src/codegen/llvm/Builder.zig | 8 +++--- src/zon.zig | 36 +++++++---------------- 8 files changed, 69 insertions(+), 87 deletions(-) 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 691ae02280f6..05d39f99de14 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2175,10 +2175,10 @@ pub const Const = struct { TargetTooSmall, }; - /// Convert self to type T. + /// 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 +2216,31 @@ 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 { + switch (@typeInfo(T)) { + .float => { + 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); + } + }, + else => @compileError("expected float type, found '" ++ @typeName(T) ++ "'"), } } @@ -2772,11 +2796,16 @@ pub const Managed = struct { pub const ConvertError = Const.ConvertError; - /// Convert self to type T. + /// 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) ConvertError!T { + return self.toConst().toFloat(T); } /// Set self from the string representation `value`. diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 60e9402673ef..a769589e5574 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1798,7 +1798,7 @@ fn parseInt( .int_literal => |int| switch (int) { .small => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), - .big => |val| return val.to(T) catch + .big => |val| return val.toInt(T) catch self.failCannotRepresent(T, node), }, .float_literal => |val| return intFromFloatExact(T, val) orelse @@ -1818,21 +1818,7 @@ fn parseFloat( switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return @floatFromInt(val), - .big => { - const main_tokens = self.ast.nodes.items(.main_token); - const tags = self.ast.nodes.items(.tag); - const data = self.ast.nodes.items(.data); - const ast_node = node.getAstNode(self.zoir); - const negative = tags[ast_node] == .negation; - const num_lit_node = if (negative) data[ast_node].lhs else ast_node; - const token = main_tokens[num_lit_node]; - const bytes = self.ast.tokenSlice(token); - const unsigned = std.fmt.parseFloat(T, bytes) catch { - // Bytes already validated by big int parser - unreachable; - }; - return if (negative) -unsigned else unsigned; - }, + .big => |val| return val.toFloat(T), }, .float_literal => |val| return @floatCast(val), .pos_inf => return std.math.inf(T), diff --git a/src/InternPool.zig b/src/InternPool.zig index 7264f0b8936f..7ac4e7b6576e 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -4357,7 +4357,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; @@ -7849,7 +7849,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; }, @@ -7866,7 +7866,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; }, @@ -7883,7 +7883,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; }, @@ -7898,7 +7898,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)), @@ -7916,7 +7916,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, @@ -7937,14 +7937,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)), @@ -7974,7 +7974,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/Value.zig b/src/Value.zig index 8962d6b7639e..96dc358ec748 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -269,7 +269,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, @@ -310,7 +310,7 @@ pub fn toSignedInt(val: Value, zcu: *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), @@ -897,7 +897,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"); @@ -914,25 +914,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); @@ -1547,7 +1528,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), @@ -4537,7 +4518,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/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index f5b05dfecd8e..87fb8bad564a 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/zon.zig b/src/zon.zig index 6b15b91870bc..ec3b6e5490fc 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -390,31 +390,17 @@ fn lowerFloat( else => unreachable, }, } }), - .big => { - const main_tokens = self.file.tree.nodes.items(.main_token); - const tags = self.file.tree.nodes.items(.tag); - const data = self.file.tree.nodes.items(.data); - const ast_node = node.getAstNode(self.file.zoir.?); - const negative = tags[ast_node] == .negation; - const num_lit_node = if (negative) data[ast_node].lhs else ast_node; - const token = main_tokens[num_lit_node]; - const bytes = self.file.tree.tokenSlice(token); - const val = std.fmt.parseFloat(f128, bytes) catch { - // Bytes already validated by big int parser - unreachable; - }; - return self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = @floatCast(val) }, - .f32_type => .{ .f32 = @floatCast(val) }, - .f64_type => .{ .f64 = @floatCast(val) }, - .f80_type => .{ .f80 = @floatCast(val) }, - .f128_type, .comptime_float_type => .{ .f128 = val }, - else => unreachable, - }, - } }); - }, + .big => |val| return self.sema.pt.intern(.{ .float = .{ + .ty = res_ty.toIntern(), + .storage = switch (res_ty.toIntern()) { + .f16_type => .{ .f16 = val.toFloat(f16) }, + .f32_type => .{ .f32 = val.toFloat(f32) }, + .f64_type => .{ .f64 = val.toFloat(f64) }, + .f80_type => .{ .f80 = val.toFloat(f80) }, + .f128_type, .comptime_float_type => .{ .f128 = val.toFloat(f128) }, + else => unreachable, + }, + } }), }, .float_literal => |val| return self.sema.pt.intern(.{ .float = .{ .ty = res_ty.toIntern(), From 874a838363f2b1444a673ab85ad50285b3580db3 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:34:35 -0800 Subject: [PATCH 34/51] Calculates mode instead of storing it --- lib/std/zig/Ast.zig | 2 +- src/Compilation.zig | 6 +++--- src/Package/Module.zig | 1 - src/Sema.zig | 2 +- src/Zcu.zig | 11 ++++------- src/Zcu/PerThread.zig | 4 +--- src/main.zig | 3 --- 7 files changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 34531ccc3ede..335168322f55 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -12,7 +12,7 @@ tokens: TokenList.Slice, /// references to the root node, this means 0 is available to indicate null. nodes: NodeList.Slice, extra_data: []Node.Index, -mode: Mode, +mode: Mode = .zig, errors: []const Error, diff --git a/src/Compilation.zig b/src/Compilation.zig index 4bc7b1af195f..a1987e432012 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2216,7 +2216,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { for (zcu.import_table.values()) |file_index| { if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue; const file = zcu.fileByIndex(file_index); - if (file.mode == .zig) { + if (file.getMode() == .zig) { comp.astgen_work_queue.writeItemAssumeCapacity(file_index); } } @@ -4257,7 +4257,7 @@ fn workerAstGenFile( wg: *WaitGroup, src: Zcu.AstGenSrc, ) void { - assert(file.mode == .zig); + assert(file.getMode() == .zig); const child_prog_node = prog_node.start(file.sub_file_path, 0); defer child_prog_node.end(); @@ -4311,7 +4311,7 @@ fn workerAstGenFile( const imported_path_digest = pt.zcu.filePathDigest(res.file_index); break :blk .{ res, imported_path_digest }; }; - if (import_result.is_new and import_result.file.mode == .zig) { + 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/Package/Module.zig b/src/Package/Module.zig index 9d782a00fc53..af91b01c9a8e 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -484,7 +484,6 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { .status = .never_loaded, .prev_status = .never_loaded, .mod = new, - .mode = .zig, }; break :b new; }; diff --git a/src/Sema.zig b/src/Sema.zig index f8ec07c7efd0..24eee32e3566 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14436,7 +14436,7 @@ 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) }); }, }; - switch (result.file.mode) { + switch (result.file.getMode()) { .zig => { try sema.declareDependency(.{ .file = result.file_index }); try pt.ensureFileAnalyzed(result.file_index); diff --git a/src/Zcu.zig b/src/Zcu.zig index 7d45162c4758..6382e0ba3f68 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -677,9 +677,6 @@ pub const File = struct { /// successful, this field is unloaded. prev_zir: ?*Zir = null, - /// Whether the file is Zig or ZON. This field is always populated. - mode: Ast.Mode, - pub const Status = enum { never_loaded, retryable_failure, @@ -699,10 +696,10 @@ pub const File = struct { root: *Package.Module, }; - pub fn modeFromPath(path: []const u8) Ast.Mode { - if (std.mem.endsWith(u8, path, ".zon")) { + 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, path, ".zig")) { + } else if (std.mem.endsWith(u8, self.sub_file_path, ".zig")) { return .zig; } else { // `Module.importFile` rejects all other extensions @@ -785,7 +782,7 @@ 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, file.mode); + file.tree = try Ast.parse(gpa, source.bytes, file.getMode()); file.tree_loaded = true; return &file.tree; } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 8c124b31c3eb..22762f97ab6c 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1876,7 +1876,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.mode == .zig); + assert(file.getMode() == .zig); assert(zcu.fileRootType(file_index) == .none); if (file.status != .success_zir) { @@ -2002,7 +2002,6 @@ pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult { .status = .never_loaded, .prev_status = .never_loaded, .mod = mod, - .mode = Zcu.File.modeFromPath(sub_file_path), }; try new_file.addReference(zcu, .{ .root = mod }); @@ -2116,7 +2115,6 @@ pub fn importFile( .status = .never_loaded, .prev_status = .never_loaded, .mod = mod, - .mode = Zcu.File.modeFromPath(sub_file_path), }; return .{ diff --git a/src/main.zig b/src/main.zig index a13e94123c8a..7bb51bbd8ea9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6106,7 +6106,6 @@ fn cmdAstCheck( .tree = undefined, .zir = undefined, .mod = undefined, - .mode = .zig, }; if (zig_source_file) |file_name| { var f = fs.cwd().openFile(file_name, .{}) catch |err| { @@ -6491,7 +6490,6 @@ fn cmdDumpZir( .tree = undefined, .zir = try Zcu.loadZirCache(gpa, f), .mod = undefined, - .mode = .zig, }; defer file.zir.deinit(gpa); @@ -6564,7 +6562,6 @@ fn cmdChangelist( .tree = undefined, .zir = undefined, .mod = undefined, - .mode = Zcu.File.modeFromPath(old_source_file), }; file.mod = try Package.Module.createLimited(arena, .{ From ee7146c7c6d605dafa9acf231beee06305d5f26f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:38:22 -0800 Subject: [PATCH 35/51] Fixes tuple in test to have non comptime fields This was a bit odd, because it meant that only the coercion really needed to be checked, which isn't what I was intending to test. --- test/behavior/zon.zig | 159 ++++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 53 deletions(-) diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index e00019a9f392..9464f2feaebd 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -200,91 +200,144 @@ test "enum literals" { } test "int" { - const expected = .{ + 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 - @as(u8, 10), - @as(i16, 24), - @as(i14, -4), - @as(i32, -123), + 10, + 24, + -4, + -123, // Test limits - @as(i8, 127), - @as(i8, -128), + 127, + -128, // Test characters - @as(u8, 'a'), - @as(u8, 'z'), + 'a', + 'z', // Test big integers - @as(u65, 36893488147419103231), - @as(u65, 36893488147419103231), - @as(i128, -18446744073709551615), // Only a big int due to negation - @as(i128, -9223372036854775809), // Only a big int due to negation + 36893488147419103231, + 36893488147419103231, + -18446744073709551615, // Only a big int due to negation + -9223372036854775809, // Only a big int due to negation // Test big integer limits - @as(i66, 36893488147419103231), - @as(i66, -36893488147419103232), + 36893488147419103231, + -36893488147419103232, // Test parsing whole number floats as integers - @as(i8, -1), - @as(i8, 123), + -1, + 123, // Test non-decimal integers - @as(i16, 0xff), - @as(i16, -0xff), - @as(i16, 0o77), - @as(i16, -0o77), - @as(i16, 0b11), - @as(i16, -0b11), + 0xff, + -0xff, + 0o77, + -0o77, + 0b11, + -0b11, // Test non-decimal big integers - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), - @as(u65, 0x1ffffffffffffffff), - @as(i66, 0x1ffffffffffffffff), - @as(i66, -0x1ffffffffffffffff), + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, + -0x1ffffffffffffffff, }; - const actual: @TypeOf(expected) = @import("zon/ints.zon"); + const actual: T = @import("zon/ints.zon"); try expectEqual(expected, actual); } test "floats" { - const expected = .{ + 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 - @as(f16, 0.5), - @as(f32, 123.456), - @as(f64, -123.456), - @as(f128, 42.5), + 0.5, + 123.456, + -123.456, + 42.5, // Test whole numbers with and without decimals - @as(f16, 5.0), - @as(f16, 5.0), - @as(f32, -102), - @as(f32, -102), + 5.0, + 5.0, + -102, + -102, // Test characters and negated characters - @as(f32, 'a'), - @as(f32, 'z'), + 'a', + 'z', // Test big integers - @as(f32, 36893488147419103231), - @as(f32, -36893488147419103231), - @as(f128, 0x1ffffffffffffffff), - @as(f32, 0x1ffffffffffffffff), + 36893488147419103231, + -36893488147419103231, + 0x1ffffffffffffffff, + 0x1ffffffffffffffff, // Exponents, underscores - @as(f32, 123.0E+77), + 123.0E+77, // Hexadecimal - @as(f32, 0x103.70p-5), - @as(f32, -0x103.70), - @as(f32, 0x1234_5678.9ABC_CDEFp-10), + 0x103.70p-5, + -0x103.70, + 0x1234_5678.9ABC_CDEFp-10, }; - const actual: @TypeOf(expected) = @import("zon/floats.zon"); + const actual: T = @import("zon/floats.zon"); try expectEqual(actual, expected); } From efd9c8f04c3cc2b04a6809ba76117715f8be5f29 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 22:24:10 -0800 Subject: [PATCH 36/51] Supports comptime fields on structs and tuples --- lib/std/zon/parse.zig | 171 +++++++++++++----- src/Sema.zig | 3 +- src/zon.zig | 142 +++++++-------- test/behavior/zon.zig | 49 +++++ ...import_zon_struct_wrong_comptime_field.zig | 16 ++ ...@import_zon_tuple_wrong_comptime_field.zig | 16 ++ test/cases/compile_errors/zon/tuple.zon | 1 + test/cases/compile_errors/zon/vec2.zon | 1 + 8 files changed, 282 insertions(+), 117 deletions(-) create mode 100644 test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig create mode 100644 test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig create mode 100644 test/cases/compile_errors/zon/tuple.zon create mode 100644 test/cases/compile_errors/zon/vec2.zon diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index a769589e5574..34278b94e996 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -594,7 +594,6 @@ fn parseUnion( switch (field_index) { inline 0...field_infos.len - 1 => |i| { if (field_infos[i].type == void) { - // XXX: remove? return self.failNode(field_val, "expected type 'void'"); } else { const value = try self.parseExpr(field_infos[i].type, options, field_val); @@ -769,11 +768,15 @@ fn parseStruct( switch (field_index) { inline 0...(field_infos.len - 1) => |j| { - @field(result, field_infos[j].name) = try self.parseExpr( - field_infos[j].type, - options, - fields.vals.at(@intCast(i)), - ); + if (field_infos[j].is_comptime) { + return self.failRuntimeValueComptimeVar(node, j); + } else { + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + options, + fields.vals.at(@intCast(i)), + ); + } }, else => unreachable, // Can't be out of bounds } @@ -881,6 +884,26 @@ test "std.zon structs" { 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 parseFromSlice(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 = parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:18: error: cannot store runtime value in compile time variable + \\ + , "{}", .{status}); + } + // Enum field (regression test, we were previously getting the field name in an // incorrect way that broke for enum values) { @@ -978,22 +1001,42 @@ fn parseTuple( }; var result: T = undefined; - const field_infos = @typeInfo(T).@"struct".fields; - if (nodes.len != field_infos.len) { - return self.failExpectedContainer(T, node); + + if (nodes.len > field_infos.len) { + return self.failNode(nodes.at(field_infos.len), std.fmt.comptimePrint( + "index {} outside of tuple length {}", + .{ field_infos.len, field_infos.len }, + )); } inline for (0..field_infos.len) |i| { - // If we fail to parse this field, free all fields before it - errdefer if (options.free_on_error) { - inline for (0..i) |j| { - if (j >= i) break; - parseFree(self.gpa, result[j]); + // Check if we're out of bounds + if (i >= nodes.len) { + if (field_infos[i].default_value) |default| { + const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); + @field(result, field_infos[i].name) = typed.*; + } else { + return self.failNode(node, std.fmt.comptimePrint( + "missing tuple field with index {}", + .{i}, + )); } - }; + } else { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + inline for (0..i) |j| { + if (j >= i) break; + parseFree(self.gpa, result[j]); + } + }; - result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); + if (field_infos[i].is_comptime) { + return self.failRuntimeValueComptimeVar(node, i); + } else { + result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); + } + } } return result; @@ -1036,7 +1079,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status}); } // Extra field @@ -1045,7 +1088,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:2: error: missing tuple field with index 1\n", "{}", .{status}); } // Tuple with unexpected field names @@ -1054,7 +1097,7 @@ test "std.zon tuples" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 1 field\n", "{}", .{status}); + try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status}); } // Struct with missing field names @@ -1065,6 +1108,26 @@ test "std.zon tuples" { try std.testing.expectError(error.ParseZon, parseFromSlice(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 parseFromSlice(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 = parseFromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{}); + try std.testing.expectError(error.ParseZon, parsed); + try std.testing.expectFmt( + \\1:9: error: cannot store runtime value in compile time variable + \\ + , "{}", .{status}); + } } fn parseArray( @@ -1082,8 +1145,21 @@ fn parseArray( const array_info = @typeInfo(T).array; // Check if the size matches - if (nodes.len != array_info.len) { - return self.failExpectedContainer(T, node); + if (nodes.len > array_info.len) { + return self.failNode(nodes.at(array_info.len), std.fmt.comptimePrint( + "index {} outside of tuple length {}", + .{ array_info.len, array_info.len }, + )); + } else if (nodes.len < array_info.len) { + switch (nodes.len) { + inline 0...array_info.len => |n| { + return self.failNode(node, std.fmt.comptimePrint( + "missing tuple field with index {}", + .{n}, + )); + }, + else => unreachable, + } } // Parse the elements and return the array @@ -1205,7 +1281,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 0 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:3: error: index 0 outside of tuple length 0\n", "{}", .{status}); } // Expect 1 find 2 @@ -1213,7 +1289,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 1 field\n", "{}", .{status}); + try std.testing.expectFmt("1:8: error: index 1 outside of tuple length 1\n", "{}", .{status}); } // Expect 2 find 1 @@ -1221,7 +1297,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 2 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:2: error: missing tuple field with index 1\n", "{}", .{status}); } // Expect 3 find 0 @@ -1229,7 +1305,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, ".{}", &status, .{})); - try std.testing.expectFmt("1:2: error: expected tuple with 3 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:2: error: missing tuple field with index 0\n", "{}", .{status}); } // Wrong inner type @@ -1258,7 +1334,7 @@ test "std.zon arrays and slices" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, "'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected tuple with 3 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } // Slice @@ -1417,7 +1493,7 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple with 4 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { @@ -1427,11 +1503,11 @@ test "std.zon string literal" { error.ParseZon, parseFromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple with 4 fields\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } - // Zero termianted slices + // Zero terminated slices { { const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\"abc\"", null, .{}); @@ -1702,28 +1778,16 @@ fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?us } } -fn failExpectedTupleWithField( - self: @This(), - node: Zoir.Node.Index, - comptime fields: usize, -) error{ParseZon} { - const plural = if (fields == 1) "" else "s"; - return self.failNode( - node, - std.fmt.comptimePrint("expected tuple with {} field{s}", .{ fields, plural }), - ); -} - fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ParseZon} { @branchHint(.cold); switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { - return self.failExpectedTupleWithField(node, @"struct".fields.len); + return self.failNode(node, "expected tuple"); } else { return self.failNode(node, "expected struct"); }, .@"union" => return self.failNode(node, "expected union"), - .array => |array| return self.failExpectedTupleWithField(node, array.len), + .array => return self.failNode(node, "expected tuple"), .pointer => |pointer| { if (pointer.child == u8 and pointer.size == .Slice and @@ -1736,8 +1800,9 @@ fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{Pa return self.failNode(node, "expected tuple"); } }, - else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + else => {}, } + @compileError("unreachable, should not be called for type " ++ @typeName(T)); } fn failMissingField(self: @This(), comptime name: []const u8, node: Zoir.Node.Index) error{ParseZon} { @@ -1754,6 +1819,24 @@ fn failDuplicateField(self: @This(), node: Zoir.Node.Index, field: usize) error{ return self.failToken(token, "duplicate field"); } +// Technically we could do this if we were willing to do a deep equal to verify +// the value matched, but doing so doesn't seem to support any real use cases +// so isn't worth the complexity at the moment. +fn failRuntimeValueComptimeVar(self: @This(), node: Zoir.Node.Index, field: usize) error{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, "cannot store runtime value in compile time variable"); +} + fn parseBool(self: @This(), node: Zoir.Node.Index) error{ParseZon}!bool { switch (node.get(self.zoir)) { .true => return true, diff --git a/src/Sema.zig b/src/Sema.zig index 24eee32e3566..1d69e85d55bd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5783,7 +5783,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, @@ -14467,6 +14467,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. result.file_index, res_ty, operand_src, + block, ); return Air.internedToRef(interned); }, diff --git a/src/zon.zig b/src/zon.zig index ec3b6e5490fc..50f6cc8b6f94 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Zcu = @import("Zcu.zig"); const Sema = @import("Sema.zig"); +const Air = @import("Air.zig"); const InternPool = @import("InternPool.zig"); const Type = @import("Type.zig"); const Zir = std.zig.Zir; @@ -23,6 +24,7 @@ sema: *Sema, file: *File, file_index: Zcu.File.Index, import_loc: LazySrcLoc, +block: *Sema.Block, /// Lowers the given file as ZON. pub fn lower( @@ -31,6 +33,7 @@ pub fn lower( file_index: Zcu.File.Index, res_ty: Type, import_loc: LazySrcLoc, + block: *Sema.Block, ) CompileError!InternPool.Index { assert(file.tree_loaded); @@ -46,6 +49,7 @@ pub fn lower( .file = file, .file_index = file_index, .import_loc = import_loc, + .block = block, }; return lower_zon.lowerExpr(.root, res_ty); @@ -600,29 +604,50 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I ), }; + const field_defaults = tuple_info.values.get(ip); const field_types = tuple_info.types.get(ip); - if (elem_nodes.len < field_types.len) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "missing tuple field with index {}", - .{elem_nodes.len}, - ); - } else if (elem_nodes.len > field_types.len) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "index {} outside tuple of length {}", - .{ - field_types.len, - elem_nodes.at(@intCast(field_types.len)), - }, - ); - } - const elems = try gpa.alloc(InternPool.Index, field_types.len); defer gpa.free(elems); + for (elems) |*v| v.* = .none; for (0..elem_nodes.len) |i| { + if (i >= elems.len) { + const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); + return self.fail( + .{ .node_abs = elem_node }, + "index {} outside tuple of length {}", + .{ + elems.len, + elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?), + }, + ); + } elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), Type.fromInterned(field_types[i])); + + if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { + const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); + return self.fail( + .{ .node_abs = elem_node }, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + } + + for (0..elems.len) |i| { + if (elems[i] == .none and i < field_defaults.len) { + elems[i] = field_defaults[i]; + } + } + + for (elems, 0..) |val, i| { + if (val == .none) { + return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "missing tuple field with index {}", + .{i}, + ); + } } return self.sema.pt.intern(.{ .aggregate = .{ @@ -648,13 +673,10 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. ), }; + const field_defaults = struct_info.field_inits.get(ip); const field_values = try gpa.alloc(InternPool.Index, struct_info.field_names.len); defer gpa.free(field_values); - - const field_defaults = struct_info.field_inits.get(ip); - for (0..field_values.len) |i| { - field_values[i] = if (i < field_defaults.len) field_defaults[i] else .none; - } + for (field_values) |*v| v.* = .none; for (0..fields.names.len) |i| { const field_name = try ip.getOrPutString( @@ -685,6 +707,24 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. } 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( + .{ .token_abs = field_name_token }, + "value stored in comptime field does not match the default value of the field", + .{}, + ); + } + } + } + + for (0..field_values.len) |i| { + if (field_values[i] == .none and i < field_defaults.len) { + field_values[i] = field_defaults[i]; + } } const field_names = struct_info.field_names.get(ip); @@ -721,28 +761,14 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool if (string_alignment and ptr_info.child == .u8_type and string_sentinel) { switch (node.get(self.file.zoir.?)) { .string_literal => |val| { - const string = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls); - const array_ty = try self.sema.pt.intern(.{ .array_type = .{ - .len = val.len, - .sentinel = .zero_u8, - .child = .u8_type, - } }); - const array_val = try self.sema.pt.intern(.{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = string }, - } }); - return self.sema.pt.intern(.{ .slice = .{ - .ty = res_ty.toIntern(), - .ptr = try self.sema.pt.intern(.{ .ptr = .{ - .ty = .manyptr_const_u8_sentinel_0_type, - .base_addr = .{ .uav = .{ - .orig_ty = .slice_const_u8_sentinel_0_type, - .val = array_val, - } }, - .byte_offset = 0, - } }), - .len = (try self.sema.pt.intValue(Type.usize, val.len)).toIntern(), - } }); + const ip_str = try ip.getOrPutString(gpa, self.sema.pt.tid, val, .maybe_embedded_nulls); + const str_ref = try self.sema.addStrLit(ip_str, val.len); + return (try self.sema.coerce( + self.block, + res_ty, + str_ref, + try self.lazySrcLoc(.{ .node_abs = node.getAstNode(self.file.zoir.?) }), + )).toInterned().?; }, else => {}, } @@ -908,31 +934,3 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I .val = val, }); } - -fn createErrorWithOptionalNote( - self: LowerZon, - src_loc: LazySrcLoc, - comptime fmt: []const u8, - args: anytype, - note: ?[]const u8, -) error{OutOfMemory}!*Zcu.ErrorMsg { - const notes = try self.sema.pt.zcu.gpa.alloc(Zcu.ErrorMsg, if (note == null) 0 else 1); - errdefer self.sema.pt.zcu.gpa.free(notes); - if (note) |n| { - notes[0] = try Zcu.ErrorMsg.init( - self.sema.pt.zcu.gpa, - src_loc, - "{s}", - .{n}, - ); - } - - const err_msg = try Zcu.ErrorMsg.create( - self.sema.pt.zcu.gpa, - src_loc, - fmt, - args, - ); - err_msg.*.notes = notes; - return err_msg; -} diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 9464f2feaebd..0f268cacd40a 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -98,6 +98,12 @@ test "struct default fields" { 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" { @@ -112,6 +118,49 @@ test "tuple" { 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"))); 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..1c670e1d0f99 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig @@ -0,0 +1,16 @@ +pub fn main() void { + const Vec2 = struct { + comptime x: f32 = 1.5, + comptime y: f32 = 2.5, + }; + const f: Vec2 = @import("zon/vec2.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/vec2.zon +// +// zon/vec2.zon:1:15: error: value stored in comptime field does not match the default value of the field +// tmp.zig:6:29: note: imported here diff --git a/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig new file mode 100644 index 000000000000..5da105883bac --- /dev/null +++ b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig @@ -0,0 +1,16 @@ +pub fn main() void { + const T = struct { + comptime f32 = 1.5, + comptime f32 = 2.5, + }; + const f: T = @import("zon/tuple.zon"); + _ = f; +} + +// error +// backend=stage2 +// output_mode=Exe +// imports=zon/tuple.zon +// +// 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/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/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 } From f03393c0b0a28f5626a367930f4300ddd79c8843 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:14:33 -0800 Subject: [PATCH 37/51] Fixes out of date comment --- src/Zcu.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Zcu.zig b/src/Zcu.zig index 6382e0ba3f68..87479544e517 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3447,8 +3447,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: *Zcu, ty: Type) ?InternPool.LoadedStructType { if (ty.ip_index == .none) return null; From 2e6bcc3eb281fc9f29050fda52f72f6595a48852 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:20:13 -0800 Subject: [PATCH 38/51] Provides migration path for to -> toInt --- lib/std/math/big/int.zig | 8 +- lib/std/math/big/int_test.zig | 438 +++++++++++++++++----------------- lib/std/math/big/rational.zig | 96 ++++---- 3 files changed, 274 insertions(+), 268 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 05d39f99de14..fc5a6cf2baf5 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2175,6 +2175,9 @@ pub const Const = struct { TargetTooSmall, }; + /// 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. @@ -2796,6 +2799,9 @@ pub const Managed = struct { pub const ConvertError = Const.ConvertError; + /// 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. @@ -2804,7 +2810,7 @@ pub const Managed = struct { } /// Convert self to float type T. - pub fn toFloat(self: Managed, comptime T: type) ConvertError!T { + pub fn toFloat(self: Managed, comptime T: type) T { return self.toConst().toFloat(T); } 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" { From a31d0c7af442c71a512265307580ce0d240a7803 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:28:31 -0800 Subject: [PATCH 39/51] Removes redundant backend option from tests, stops using mode exe --- test/cases/compile_errors/@import_zon_addr_slice.zig | 2 -- test/cases/compile_errors/@import_zon_array_len.zig | 4 +--- test/cases/compile_errors/@import_zon_bad_import.zig | 4 +--- test/cases/compile_errors/@import_zon_coerce_pointer.zig | 4 +--- test/cases/compile_errors/@import_zon_doc_comment.zig | 4 +--- .../compile_errors/@import_zon_double_negation_float.zig | 4 +--- test/cases/compile_errors/@import_zon_double_negation_int.zig | 4 +--- test/cases/compile_errors/@import_zon_enum_embedded_null.zig | 4 +--- test/cases/compile_errors/@import_zon_expected_void.zig | 4 +--- test/cases/compile_errors/@import_zon_invalid_character.zig | 4 +--- test/cases/compile_errors/@import_zon_invalid_number.zig | 4 +--- test/cases/compile_errors/@import_zon_invalid_string.zig | 4 +--- .../compile_errors/@import_zon_leading_zero_in_integer.zig | 4 +--- test/cases/compile_errors/@import_zon_neg_char.zig | 4 +--- test/cases/compile_errors/@import_zon_neg_nan.zig | 4 +--- test/cases/compile_errors/@import_zon_negative_zero.zig | 4 +--- test/cases/compile_errors/@import_zon_no_rt.zig | 4 +--- test/cases/compile_errors/@import_zon_number_fail_limits.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_char_0.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_char_1.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_int_0.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_int_1.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_int_2.zig | 4 +--- test/cases/compile_errors/@import_zon_oob_int_3.zig | 4 +--- test/cases/compile_errors/@import_zon_struct_dup_field.zig | 4 +--- .../@import_zon_struct_wrong_comptime_field.zig | 4 +--- test/cases/compile_errors/@import_zon_syntax_error.zig | 4 +--- .../compile_errors/@import_zon_tuple_wrong_comptime_field.zig | 4 +--- test/cases/compile_errors/@import_zon_type_decl.zig | 4 +--- test/cases/compile_errors/@import_zon_type_expr_array.zig | 4 +--- test/cases/compile_errors/@import_zon_type_expr_fn.zig | 4 +--- test/cases/compile_errors/@import_zon_type_expr_struct.zig | 4 +--- test/cases/compile_errors/@import_zon_type_expr_tuple.zig | 4 +--- test/cases/compile_errors/@import_zon_type_mismatch.zig | 4 +--- test/cases/compile_errors/@import_zon_unescaped_newline.zig | 4 +--- test/cases/compile_errors/@import_zon_unknown_ident.zig | 4 +--- test/cases/compile_errors/@import_zon_void.zig | 4 +--- 37 files changed, 36 insertions(+), 110 deletions(-) diff --git a/test/cases/compile_errors/@import_zon_addr_slice.zig b/test/cases/compile_errors/@import_zon_addr_slice.zig index 48d50cbacf0d..df7235d31acc 100644 --- a/test/cases/compile_errors/@import_zon_addr_slice.zig +++ b/test/cases/compile_errors/@import_zon_addr_slice.zig @@ -4,8 +4,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // 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 index 342504d5540b..dfe5cbb0b985 100644 --- a/test/cases/compile_errors/@import_zon_array_len.zig +++ b/test/cases/compile_errors/@import_zon_array_len.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: [4]u8 = @import("zon/array.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/array.zon // // array.zon:1:2: error: expected type '[4]u8' diff --git a/test/cases/compile_errors/@import_zon_bad_import.zig b/test/cases/compile_errors/@import_zon_bad_import.zig index a84f24dd8250..b992506f74f0 100644 --- a/test/cases/compile_errors/@import_zon_bad_import.zig +++ b/test/cases/compile_errors/@import_zon_bad_import.zig @@ -1,12 +1,10 @@ -pub fn main() void { +export fn entry() void { _ = @import( "bogus-does-not-exist.zon", ); } // error -// backend=stage2 // target=native -// output_mode=Exe // // :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig index 78e4318733f5..9cbae754045a 100644 --- a/test/cases/compile_errors/@import_zon_coerce_pointer.zig +++ b/test/cases/compile_errors/@import_zon_coerce_pointer.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: *struct { u8, u8, u8 } = @import("zon/array.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/array.zon // // array.zon:1:2: error: non slice pointers are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_doc_comment.zig b/test/cases/compile_errors/@import_zon_doc_comment.zig index 00d6f0a8f1d7..126f292652b5 100644 --- a/test/cases/compile_errors/@import_zon_doc_comment.zig +++ b/test/cases/compile_errors/@import_zon_doc_comment.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: struct { foo: type } = @import("zon/doc_comment.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index a89088513b17..e3263dd7c611 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_float.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_float.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: f32 = @import("zon/double_negation_float.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index 07e888e390d1..9c3b3bd96e15 100644 --- a/test/cases/compile_errors/@import_zon_double_negation_int.zig +++ b/test/cases/compile_errors/@import_zon_double_negation_int.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: i32 = @import("zon/double_negation_int.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index b4dc7b542bb5..a56182e87f4e 100644 --- a/test/cases/compile_errors/@import_zon_enum_embedded_null.zig +++ b/test/cases/compile_errors/@import_zon_enum_embedded_null.zig @@ -1,13 +1,11 @@ const std = @import("std"); -pub fn main() void { +export fn entry() void { const E = enum { foo }; const f: struct { E, E } = @import("zon/enum_embedded_null.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index d6216ad60b15..82f446a914c0 100644 --- a/test/cases/compile_errors/@import_zon_expected_void.zig +++ b/test/cases/compile_errors/@import_zon_expected_void.zig @@ -1,12 +1,10 @@ -pub fn main() void { +export fn entry() void { const U = union(enum) { a: void }; const f: U = @import("zon/simple_union.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/simple_union.zon // // simple_union.zon:1:9: error: expected type 'void' diff --git a/test/cases/compile_errors/@import_zon_invalid_character.zig b/test/cases/compile_errors/@import_zon_invalid_character.zig index 6c1efffaba13..c169db8a37b2 100644 --- a/test/cases/compile_errors/@import_zon_invalid_character.zig +++ b/test/cases/compile_errors/@import_zon_invalid_character.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u8 = @import("zon/invalid_character.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index a18f1631de2f..740628b3eb79 100644 --- a/test/cases/compile_errors/@import_zon_invalid_number.zig +++ b/test/cases/compile_errors/@import_zon_invalid_number.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u128 = @import("zon/invalid_number.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index 331804a6f115..0ae367e4ae81 100644 --- a/test/cases/compile_errors/@import_zon_invalid_string.zig +++ b/test/cases/compile_errors/@import_zon_invalid_string.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: []const u8 = @import("zon/invalid_string.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index d901d5621a92..5e5b1e1a1edf 100644 --- a/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig +++ b/test/cases/compile_errors/@import_zon_leading_zero_in_integer.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u128 = @import("zon/leading_zero_in_integer.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/leading_zero_in_integer.zon // // leading_zero_in_integer.zon:1:1: error: number '0012' has leading zero diff --git a/test/cases/compile_errors/@import_zon_neg_char.zig b/test/cases/compile_errors/@import_zon_neg_char.zig index 2cf8b8b18d30..e5239c14bdeb 100644 --- a/test/cases/compile_errors/@import_zon_neg_char.zig +++ b/test/cases/compile_errors/@import_zon_neg_char.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u8 = @import("zon/neg_char.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index be623bf2b0c2..acc88a80c8fa 100644 --- a/test/cases/compile_errors/@import_zon_neg_nan.zig +++ b/test/cases/compile_errors/@import_zon_neg_nan.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u8 = @import("zon/neg_nan.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index cb3499c98bf0..69918c926e15 100644 --- a/test/cases/compile_errors/@import_zon_negative_zero.zig +++ b/test/cases/compile_errors/@import_zon_negative_zero.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: i8 = @import("zon/negative_zero.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/negative_zero.zon // // negative_zero.zon:1:2: error: integer literal '-0' is ambiguous diff --git a/test/cases/compile_errors/@import_zon_no_rt.zig b/test/cases/compile_errors/@import_zon_no_rt.zig index 32e0bc7ad8eb..b4d1dc1228e9 100644 --- a/test/cases/compile_errors/@import_zon_no_rt.zig +++ b/test/cases/compile_errors/@import_zon_no_rt.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f = @import("zon/simple_union.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/simple_union.zon // // tmp.zig:2:23: error: import 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 index 9d04ce1b74ad..ee6cc2c2d6ab 100644 --- a/test/cases/compile_errors/@import_zon_number_fail_limits.zig +++ b/test/cases/compile_errors/@import_zon_number_fail_limits.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: i66 = @import("zon/large_number.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/large_number.zon // // large_number.zon:1:1: error: type 'i66' cannot represent integer value '36893488147419103232' diff --git a/test/cases/compile_errors/@import_zon_oob_char_0.zig b/test/cases/compile_errors/@import_zon_oob_char_0.zig index 31931dbda9e5..5c318cc1b875 100644 --- a/test/cases/compile_errors/@import_zon_oob_char_0.zig +++ b/test/cases/compile_errors/@import_zon_oob_char_0.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { { const f: u6 = @import("zon/char_32.zon"); _ = f; @@ -10,8 +10,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/char_32.zon // // char_32.zon:1:1: error: type 'u5' cannot represent integer value '32' diff --git a/test/cases/compile_errors/@import_zon_oob_char_1.zig b/test/cases/compile_errors/@import_zon_oob_char_1.zig index 67c7866408bd..1a6fffac8642 100644 --- a/test/cases/compile_errors/@import_zon_oob_char_1.zig +++ b/test/cases/compile_errors/@import_zon_oob_char_1.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { { const f: i7 = @import("zon/char_32.zon"); _ = f; @@ -10,8 +10,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/char_32.zon // // char_32.zon:1:1: error: type 'i6' cannot represent integer value '32' diff --git a/test/cases/compile_errors/@import_zon_oob_int_0.zig b/test/cases/compile_errors/@import_zon_oob_int_0.zig index 1ac2160512a3..a830710d9111 100644 --- a/test/cases/compile_errors/@import_zon_oob_int_0.zig +++ b/test/cases/compile_errors/@import_zon_oob_int_0.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { { const f: u6 = @import("zon/int_32.zon"); _ = f; @@ -10,8 +10,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/int_32.zon // // int_32.zon:1:1: error: type 'u5' cannot represent integer value '32' diff --git a/test/cases/compile_errors/@import_zon_oob_int_1.zig b/test/cases/compile_errors/@import_zon_oob_int_1.zig index 75757b980df6..c79e586eccac 100644 --- a/test/cases/compile_errors/@import_zon_oob_int_1.zig +++ b/test/cases/compile_errors/@import_zon_oob_int_1.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { { const f: i7 = @import("zon/int_32.zon"); _ = f; @@ -10,8 +10,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/int_32.zon // // int_32.zon:1:1: error: type 'i6' cannot represent integer value '32' diff --git a/test/cases/compile_errors/@import_zon_oob_int_2.zig b/test/cases/compile_errors/@import_zon_oob_int_2.zig index c1097da3cd29..a78c533dcd81 100644 --- a/test/cases/compile_errors/@import_zon_oob_int_2.zig +++ b/test/cases/compile_errors/@import_zon_oob_int_2.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { { const f: i7 = @import("zon/int_neg_33.zon"); _ = f; @@ -10,8 +10,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/int_neg_33.zon // // int_neg_33.zon:1:1: error: type 'i6' cannot represent integer value '-33' diff --git a/test/cases/compile_errors/@import_zon_oob_int_3.zig b/test/cases/compile_errors/@import_zon_oob_int_3.zig index 7240fc6033be..ee2015677ee1 100644 --- a/test/cases/compile_errors/@import_zon_oob_int_3.zig +++ b/test/cases/compile_errors/@import_zon_oob_int_3.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: u64 = @import("zon/int_neg_33.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/int_neg_33.zon // // int_neg_33.zon:1:1: error: type 'u64' cannot represent integer value '-33' diff --git a/test/cases/compile_errors/@import_zon_struct_dup_field.zig b/test/cases/compile_errors/@import_zon_struct_dup_field.zig index fa35aa268fb0..ff9cb12f82ff 100644 --- a/test/cases/compile_errors/@import_zon_struct_dup_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -1,12 +1,10 @@ const std = @import("std"); -pub fn main() void { +export fn entry() void { const f: struct { name: u8 } = @import("zon/struct_dup_field.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/struct_dup_field.zon // // struct_dup_field.zon:3:6: error: duplicate field 'name' 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 index 1c670e1d0f99..d9a0f76b2c6f 100644 --- a/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { const Vec2 = struct { comptime x: f32 = 1.5, comptime y: f32 = 2.5, @@ -8,8 +8,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/vec2.zon // // zon/vec2.zon:1:15: error: value stored in comptime field does not match the default value of the field diff --git a/test/cases/compile_errors/@import_zon_syntax_error.zig b/test/cases/compile_errors/@import_zon_syntax_error.zig index 0035b5da288b..c4719c346188 100644 --- a/test/cases/compile_errors/@import_zon_syntax_error.zig +++ b/test/cases/compile_errors/@import_zon_syntax_error.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: bool = @import("zon/syntax_error.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index 5da105883bac..c5ff4c0ee371 100644 --- a/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig +++ b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig @@ -1,4 +1,4 @@ -pub fn main() void { +export fn entry() void { const T = struct { comptime f32 = 1.5, comptime f32 = 2.5, @@ -8,8 +8,6 @@ pub fn main() void { } // error -// backend=stage2 -// output_mode=Exe // imports=zon/tuple.zon // // zon/tuple.zon:1:9: error: value stored in comptime field does not match the default value of the field diff --git a/test/cases/compile_errors/@import_zon_type_decl.zig b/test/cases/compile_errors/@import_zon_type_decl.zig index 687c3f195abf..add73f22e64c 100644 --- a/test/cases/compile_errors/@import_zon_type_decl.zig +++ b/test/cases/compile_errors/@import_zon_type_decl.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: struct { foo: type } = @import("zon/type_decl.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index 2074cd37d378..2272738328c0 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_array.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_array.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: [3]i32 = @import("zon/type_expr_array.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/type_expr_array.zon // // type_expr_array.zon:1:1: error: types are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_type_expr_fn.zig b/test/cases/compile_errors/@import_zon_type_expr_fn.zig index 87c42ede6b46..d2e205e29600 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_fn.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_fn.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: i32 = @import("zon/type_expr_fn.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/type_expr_fn.zon // // type_expr_fn.zon:1:1: error: types are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_type_expr_struct.zig b/test/cases/compile_errors/@import_zon_type_expr_struct.zig index 6eedf32d4ad5..e764d0ac19ec 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_struct.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_struct.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: struct { x: f32, y: f32 } = @import("zon/type_expr_struct.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/type_expr_struct.zon // // type_expr_struct.zon:1:1: error: types are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig index 17c8725ae6cd..63466e1c8ecf 100644 --- a/test/cases/compile_errors/@import_zon_type_expr_tuple.zig +++ b/test/cases/compile_errors/@import_zon_type_expr_tuple.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: struct { f32, f32 } = @import("zon/type_expr_tuple.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/type_expr_tuple.zon // // type_expr_tuple.zon:1:1: error: types are not available in ZON diff --git a/test/cases/compile_errors/@import_zon_type_mismatch.zig b/test/cases/compile_errors/@import_zon_type_mismatch.zig index 530ee5c147a6..38a50ee7d98d 100644 --- a/test/cases/compile_errors/@import_zon_type_mismatch.zig +++ b/test/cases/compile_errors/@import_zon_type_mismatch.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: bool = @import("zon/struct.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/struct.zon // // struct.zon:1:2: error: expected type 'bool' diff --git a/test/cases/compile_errors/@import_zon_unescaped_newline.zig b/test/cases/compile_errors/@import_zon_unescaped_newline.zig index 54fd1a5f86cb..160095c0c24a 100644 --- a/test/cases/compile_errors/@import_zon_unescaped_newline.zig +++ b/test/cases/compile_errors/@import_zon_unescaped_newline.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: i8 = @import("zon/unescaped_newline.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // 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 index f3eb2e95ed79..cf3ff652b553 100644 --- a/test/cases/compile_errors/@import_zon_unknown_ident.zig +++ b/test/cases/compile_errors/@import_zon_unknown_ident.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: struct { value: bool } = @import("zon/unknown_ident.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/unknown_ident.zon // // unknown_ident.zon:2:14: error: invalid expression diff --git a/test/cases/compile_errors/@import_zon_void.zig b/test/cases/compile_errors/@import_zon_void.zig index f09d6ccded01..327fe74303f5 100644 --- a/test/cases/compile_errors/@import_zon_void.zig +++ b/test/cases/compile_errors/@import_zon_void.zig @@ -1,11 +1,9 @@ -pub fn main() void { +export fn entry() void { const f: union { foo: void } = @import("zon/void.zon"); _ = f; } // error -// backend=stage2 -// output_mode=Exe // imports=zon/void.zon // // void.zon:1:11: error: void literals are not available in ZON From da70d4220578dd35b96f2593408c9bf430dd200a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 16:05:20 -0800 Subject: [PATCH 40/51] Stops requiring tree to be loaded unless errors occur --- src/zon.zig | 52 ++++------------------------------------------------ 1 file changed, 4 insertions(+), 48 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 50f6cc8b6f94..eb22ffbae8cf 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -35,8 +35,6 @@ pub fn lower( import_loc: LazySrcLoc, block: *Sema.Block, ) CompileError!InternPool.Index { - assert(file.tree_loaded); - const zoir = try file.getZoir(sema.gpa); if (zoir.hasCompileErrors()) { @@ -92,50 +90,6 @@ const Ident = struct { } }; -fn ident(self: LowerZon, token: Ast.TokenIndex) !Ident { - var bytes = self.file.tree.tokenSlice(token); - - if (bytes[0] == '@' and bytes[1] == '"') { - const gpa = self.sema.gpa; - - const raw_string = bytes[1..bytes.len]; - var parsed: std.ArrayListUnmanaged(u8) = .{}; - defer parsed.deinit(gpa); - - switch (try std.zig.string_literal.parseWrite(parsed.writer(gpa), raw_string)) { - .success => { - if (std.mem.indexOfScalar(u8, parsed.items, 0) != null) { - return self.fail(.{ .token_abs = token }, "identifier cannot contain null bytes", .{}); - } - return .{ - .bytes = try parsed.toOwnedSlice(gpa), - .owned = true, - }; - }, - .failure => |err| { - const offset = self.file.tree.tokens.items(.start)[token]; - return self.fail( - .{ .byte_abs = offset + @as(u32, @intCast(err.offset())) }, - "{}", - .{err.fmtWithSource(raw_string)}, - ); - }, - } - } - - return .{ - .bytes = bytes, - .owned = false, - }; -} - -fn identAsNullTerminatedString(self: LowerZon, token: Ast.TokenIndex) !NullTerminatedString { - var parsed = try self.ident(token); - defer parsed.deinit(self.sema.gpa); - const ip = &self.sema.pt.zcu.intern_pool; - return ip.getOrPutString(self.sema.gpa, self.sema.pt.tid, parsed.bytes, .no_embedded_nulls); -} - const FieldTypes = union(enum) { st: struct { ty: Type, @@ -686,8 +640,6 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. .no_embedded_nulls, ); const field_node = fields.vals.at(@intCast(i)); - const field_node_ast = field_node.getAstNode(self.file.zoir.?); - const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; const name_index = struct_info.nameIndex(ip, field_name) orelse { return self.fail( @@ -699,6 +651,8 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const field_type = Type.fromInterned(struct_info.field_types.get(ip)[name_index]); if (field_values[name_index] != .none) { + const field_node_ast = field_node.getAstNode(self.file.zoir.?); + const field_name_token = self.file.getTree(gpa).firstToken(field_node_ast) - 2; return self.fail( .{ .token_abs = field_name_token }, "duplicate field '{}'", @@ -712,6 +666,8 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const val = ip.indexToKey(field_values[name_index]); const default = ip.indexToKey(field_defaults[name_index]); if (!val.eql(default, ip)) { + const field_node_ast = field_node.getAstNode(self.file.zoir.?); + const field_name_token = self.file.getTree(gpa).firstToken(field_node_ast) - 2; return self.fail( .{ .token_abs = field_name_token }, "value stored in comptime field does not match the default value of the field", From 88c533eceb4bc5bf9c8457c9b885e8076569a504 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 17:57:45 -0800 Subject: [PATCH 41/51] Resolves unecessary copying of strings between ZonGen & parse Will give API more thought in followup --- lib/std/zig/ZonGen.zig | 142 +++++++++++------ lib/std/zig/string_literal.zig | 22 ++- lib/std/zon/parse.zig | 148 +++++++++++++----- src/Zcu.zig | 2 +- src/fmt.zig | 4 +- src/main.zig | 2 +- src/zon.zig | 5 +- ...import_zon_struct_wrong_comptime_field.zig | 2 +- ...@import_zon_tuple_wrong_comptime_field.zig | 2 +- 9 files changed, 236 insertions(+), 93 deletions(-) diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 6eab3b563512..92869cb31721 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -3,6 +3,8 @@ gpa: Allocator, tree: Ast, +parse_str_lits: bool, + nodes: std.MultiArrayList(Zoir.Node.Repr), extra: std.ArrayListUnmanaged(u32), limbs: std.ArrayListUnmanaged(std.math.big.Limb), @@ -12,12 +14,13 @@ 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 fn generate(gpa: Allocator, tree: Ast, parse_str_lits: bool) Allocator.Error!Zoir { assert(tree.mode == .zon); var zg: ZonGen = .{ .gpa = gpa, .tree = tree, + .parse_str_lits = parse_str_lits, .nodes = .empty, .extra = .empty, .limbs = .empty, @@ -429,46 +432,6 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator } } -fn parseStrLit(zg: *ZonGen, token: Ast.TokenIndex, offset: u32) !u32 { - const raw_string = zg.tree.tokenSlice(token)[offset..]; - const start = zg.string_bytes.items.len; - switch (try std.zig.string_literal.parseWrite(zg.string_bytes.writer(zg.gpa), raw_string)) { - .success => return @intCast(start), - .failure => |err| { - try zg.lowerStrLitError(err, token, raw_string, offset); - return error.BadString; - }, - } -} - -fn parseMultilineStrLit(zg: *ZonGen, node: Ast.Node.Index) !u32 { - const gpa = zg.gpa; - const tree = zg.tree; - const string_bytes = &zg.string_bytes; - - const first_tok, const last_tok = bounds: { - const node_data = tree.nodes.items(.data)[node]; - break :bounds .{ node_data.lhs, node_data.rhs }; - }; - - const str_index: u32 = @intCast(string_bytes.items.len); - - // First line: do not append a newline. - { - const line_bytes = tree.tokenSlice(first_tok)[2..]; - try string_bytes.appendSlice(gpa, line_bytes); - } - // Following lines: each line prepends a newline. - for (first_tok + 1..last_tok + 1) |tok_idx| { - const line_bytes = tree.tokenSlice(@intCast(tok_idx))[2..]; - try string_bytes.ensureUnusedCapacity(gpa, line_bytes.len + 1); - string_bytes.appendAssumeCapacity('\n'); - string_bytes.appendSliceAssumeCapacity(line_bytes); - } - - return @intCast(str_index); -} - fn appendIdentStr(zg: *ZonGen, ident_token: Ast.TokenIndex) !u32 { const tree = zg.tree; assert(tree.tokens.items(.tag)[ident_token] == .identifier); @@ -478,7 +441,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", .{}); @@ -491,19 +465,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.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, diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 716a9b90f01c..73313d63dd6e 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -43,7 +43,7 @@ pub const Error = union(enum) { pub fn lower( err: Error, raw_string: []const u8, - offset: u32, + index_offset: u32, comptime func: anytype, first_args: anytype, ) @typeInfo(@TypeOf(func)).@"fn".return_type.? { @@ -61,18 +61,34 @@ pub const Error = union(enum) { .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]} }, + .expected_single_quote => .{ "expected single 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, + index_offset + bad_index, fmt_str, args, }); }, } } + + 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/parse.zig b/lib/std/zon/parse.zig index 34278b94e996..82d615d67cef 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -5,7 +5,7 @@ const Zoir = std.zig.Zoir; const ZonGen = std.zig.ZonGen; const TokenIndex = std.zig.Ast.TokenIndex; const Base = std.zig.number_literal.Base; -const StringLiteralError = std.zig.string_literal.Error; +const StrLitErr = std.zig.string_literal.Error; const NumberLiteralError = std.zig.number_literal.Error; const assert = std.debug.assert; const ArrayListUnmanaged = std.ArrayListUnmanaged; @@ -51,10 +51,25 @@ pub const Error = union(enum) { } }; - pub fn getMessage(self: Note, status: *const Status) []const u8 { - switch (self) { - .zoir => |note| return note.msg.get(status.zoir.?), - } + fn formatMessage( + self: []const u8, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = f; + _ = options; + + // Just writes the string for now, but we're keeping this behind a formatter so we have + // the option to extend it in the future to print more advanced messages (like `Error` + // does) without breaking the API. + try writer.writeAll(self); + } + + pub fn fmtMessage(self: Note, status: *const Status) std.fmt.Formatter(Note.formatMessage) { + return .{ .data = switch (self) { + .zoir => |note| note.msg.get(status.zoir.?), + } }; } pub fn getLocation(self: Note, status: *const Status) Ast.Location { @@ -95,16 +110,59 @@ pub const Error = union(enum) { const TypeCheckFailure = struct { token: Ast.TokenIndex, - message: []const u8, + detail: union(enum) { + msg: []const u8, + str_lit_err: StrLitErr, + }, }; - pub fn getMessage(self: @This(), status: *const Status) []const u8 { - return switch (self) { - .zoir => |err| err.msg.get(status.zoir.?), - .type_check => |err| err.message, + const FormatMessage = struct { + err: Error, + status: *const Status, + }; + + fn formatMessage( + self: FormatMessage, + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = f; + _ = options; + return switch (self.err) { + .zoir => |err| try writer.writeAll(err.msg.get(self.status.zoir.?)), + .type_check => |tc| switch (tc.detail) { + .msg => |msg| try writer.writeAll(msg), + .str_lit_err => |str_lit_err| { + const raw_string = self.status.ast.?.tokenSlice(tc.token); + return str_lit_err.lower( + raw_string, + 0, + struct { + fn lowerStrLitErr( + w: @TypeOf(writer), + offset: u32, + comptime format: []const u8, + args: anytype, + ) @TypeOf(w).Error!void { + _ = offset; + try w.print(format, args); + } + }.lowerStrLitErr, + .{writer}, + ); + }, + }, }; } + 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) { @@ -113,7 +171,13 @@ pub const Error = union(enum) { err.token, err.node_or_offset, ), - .type_check => |err| return ast.tokenLocation(0, err.token), + .type_check => |err| { + const offset = switch (err.detail) { + .msg => 0, + .str_lit_err => |sle| sle.offset(), + }; + return ast.tokenLocation(@intCast(offset), err.token); + }, }; } @@ -168,13 +232,13 @@ pub const Status = struct { var errors = self.iterateErrors(); while (errors.next()) |err| { const loc = err.getLocation(self); - const msg = err.getMessage(self); - try writer.print("{}:{}: error: {s}\n", .{ loc.line + 1, loc.column + 1, msg }); + 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.getMessage(self); + const note_msg = note.fmtMessage(self); try writer.print("{}:{}: note: {s}\n", .{ note_loc.line + 1, note_loc.column + 1, note_msg }); } } @@ -266,7 +330,7 @@ pub fn parseFromSlice( defer if (status == null) ast.deinit(gpa); if (status) |s| s.ast = ast; - var zoir = try ZonGen.generate(gpa, ast); + var zoir = try ZonGen.generate(gpa, ast, false); defer if (status == null) zoir.deinit(gpa); if (status) |s| s.zoir = zoir; if (zoir.hasCompileErrors()) return error.ParseZon; @@ -307,7 +371,7 @@ pub fn parseFromZoirNoAlloc( test "std.zon parseFromZoirNoAlloc" { var ast = try std.zig.Ast.parse(std.testing.allocator, ".{ .x = 1.5, .y = 2.5 }", .zon); defer ast.deinit(std.testing.allocator); - var zoir = try ZonGen.generate(std.testing.allocator, ast); + var zoir = try ZonGen.generate(std.testing.allocator, ast, false); defer zoir.deinit(std.testing.allocator); const S = struct { x: f32, y: f32 }; const found = try parseFromZoirNoAlloc(S, ast, zoir, null, .{}); @@ -369,7 +433,7 @@ test "std.zon parseFromZoirNode and parseFromZoirNodeNoAlloc" { var ast = try std.zig.Ast.parse(gpa, ".{ .vec = .{ .x = 1.5, .y = 2.5 } }", .zon); defer ast.deinit(gpa); - var zoir = try ZonGen.generate(gpa, ast); + var zoir = try ZonGen.generate(gpa, ast, false); defer zoir.deinit(gpa); const vec = Zoir.Node.Index.root.get(zoir).struct_literal.vals.at(0); @@ -1362,7 +1426,7 @@ fn parsePointer( node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon }!T { switch (node.get(self.zoir)) { - .string_literal => |str| return try self.parseString(T, node, str), + .string_literal => return try self.parseString(T, node), .array_literal => |nodes| return try self.parseSlice(T, options, nodes), .empty_literal => return try self.parseSlice(T, options, .{ .start = node, .len = 0 }), else => return self.failExpectedContainer(T, node), @@ -1373,9 +1437,26 @@ fn parseString( self: *@This(), comptime T: type, node: Zoir.Node.Index, - str: []const u8, ) !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| { + @branchHint(.cold); + const token = self.ast.nodes.items(.main_token)[ast_node]; + if (self.status) |s| s.type_check = .{ + .detail = .{ .str_lit_err = err }, + .token = token, + }; + return error.ParseZon; + }, + } if (pointer.child != u8 or pointer.size != .Slice or @@ -1386,14 +1467,11 @@ fn parseString( return self.failExpectedContainer(T, node); } - if (pointer.sentinel) |sentinel| { - if (@as(*const u8, @ptrCast(sentinel)).* != 0) { - return self.failExpectedContainer(T, node); - } - return try self.gpa.dupeZ(u8, str); + if (pointer.sentinel != null) { + return try buf.toOwnedSliceSentinel(self.gpa, 0); + } else { + return try buf.toOwnedSlice(self.gpa); } - - return self.gpa.dupe(pointer.child, str); } fn parseSlice( @@ -1485,7 +1563,7 @@ test "std.zon string literal" { { var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); defer ast.deinit(gpa); - var zoir = try ZonGen.generate(gpa, ast); + var zoir = try ZonGen.generate(gpa, ast, false); defer zoir.deinit(gpa); var status: Status = .{}; defer status.deinit(gpa); @@ -1728,20 +1806,20 @@ test "std.zon enum literals" { } } -fn failToken(self: @This(), token: Ast.TokenIndex, message: []const u8) error{ParseZon} { +fn failToken(self: @This(), token: Ast.TokenIndex, msg: []const u8) error{ParseZon} { @branchHint(.cold); if (self.status) |s| s.type_check = .{ .token = token, - .message = message, + .detail = .{ .msg = msg }, }; return error.ParseZon; } -fn failNode(self: @This(), node: Zoir.Node.Index, message: []const u8) error{ParseZon} { +fn failNode(self: @This(), node: Zoir.Node.Index, msg: []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, message); + return self.failToken(token, msg); } fn failCannotRepresent(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon} { @@ -1765,13 +1843,13 @@ fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?us if (info.fields.len == 0) { return self.failToken(token, "unexpected field, no fields expected"); } else { - comptime var message: []const u8 = "unexpected field, supported fields: "; + comptime var msg: []const u8 = "unexpected field, supported fields: "; inline for (info.fields, 0..) |field_info, i| { - if (i != 0) message = message ++ ", "; + if (i != 0) msg = msg ++ ", "; const id_formatter = comptime std.zig.fmtId(field_info.name); - message = message ++ std.fmt.comptimePrint("{}", .{id_formatter}); + msg = msg ++ std.fmt.comptimePrint("{}", .{id_formatter}); } - return self.failToken(token, message); + return self.failToken(token, msg); } }, else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), diff --git a/src/Zcu.zig b/src/Zcu.zig index 87479544e517..95640a75f785 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -791,7 +791,7 @@ pub const File = struct { if (file.zoir) |*zoir| return zoir; assert(file.tree_loaded); assert(file.tree.mode == .zon); - file.zoir = try ZonGen.generate(gpa, file.tree); + file.zoir = try ZonGen.generate(gpa, file.tree, true); return &file.zoir.?; } diff --git a/src/fmt.zig b/src/fmt.zig index 9ab581cad471..8b79efbafcb9 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, true); 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, true); defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { diff --git a/src/main.zig b/src/main.zig index 7bb51bbd8ea9..c765b874faac 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6240,7 +6240,7 @@ fn cmdAstCheck( } }, .zon => { - const zoir = try ZonGen.generate(gpa, file.tree); + const zoir = try ZonGen.generate(gpa, file.tree, true); defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { diff --git a/src/zon.zig b/src/zon.zig index eb22ffbae8cf..976737afc228 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -35,6 +35,7 @@ pub fn lower( import_loc: LazySrcLoc, block: *Sema.Block, ) CompileError!InternPool.Index { + assert(file.tree_loaded); const zoir = try file.getZoir(sema.gpa); if (zoir.hasCompileErrors()) { @@ -652,7 +653,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const field_type = Type.fromInterned(struct_info.field_types.get(ip)[name_index]); if (field_values[name_index] != .none) { const field_node_ast = field_node.getAstNode(self.file.zoir.?); - const field_name_token = self.file.getTree(gpa).firstToken(field_node_ast) - 2; + const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; return self.fail( .{ .token_abs = field_name_token }, "duplicate field '{}'", @@ -667,7 +668,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const default = ip.indexToKey(field_defaults[name_index]); if (!val.eql(default, ip)) { const field_node_ast = field_node.getAstNode(self.file.zoir.?); - const field_name_token = self.file.getTree(gpa).firstToken(field_node_ast) - 2; + const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; return self.fail( .{ .token_abs = field_name_token }, "value stored in comptime field does not match the default value of the field", 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 index d9a0f76b2c6f..ef818499d6e4 100644 --- a/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig @@ -10,5 +10,5 @@ export fn entry() void { // error // imports=zon/vec2.zon // -// zon/vec2.zon:1:15: error: value stored in comptime field does not match the default value of the field +// vec2.zon:1:15: error: value stored in comptime field does not match the default value of the field // tmp.zig:6:29: note: imported here diff --git a/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig index c5ff4c0ee371..f1cf5896f94b 100644 --- a/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig +++ b/test/cases/compile_errors/@import_zon_tuple_wrong_comptime_field.zig @@ -10,5 +10,5 @@ export fn entry() void { // error // imports=zon/tuple.zon // -// zon/tuple.zon:1:9: error: value stored in comptime field does not match the default value of the field +// 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 From d59b96f60b397d79196dd65aaee98de5e252b64d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 18:51:12 -0800 Subject: [PATCH 42/51] Simplifies error formatting --- lib/std/zig/AstGen.zig | 16 +- lib/std/zig/ZonGen.zig | 16 +- lib/std/zig/string_literal.zig | 86 ++-- lib/std/zon.zig | 3 - lib/std/zon/parse.zig | 700 ++++++++++++++++++++++----------- 5 files changed, 558 insertions(+), 263 deletions(-) diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index e6a4f0d77d5a..61601cd7f1aa 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -11518,9 +11518,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 AstGen.failOff( + astgen, + token, + @intCast(offset + err.offset()), + "{}", + .{err.fmt(raw_string)}, + ); } fn failNode( diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 92869cb31721..8fcd3d8cec79 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -741,8 +741,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 73313d63dd6e..2dff70d70b9e 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -39,41 +39,67 @@ 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, - index_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 single 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 ++ .{ - index_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, diff --git a/lib/std/zon.zig b/lib/std/zon.zig index 4288c52729ab..0ade6ae9552c 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -44,7 +44,6 @@ //! * `parseFromZoir` //! * `parseFromZoirNode` //! * `parseFromZoirNode` -//! * `parseFromZoirNodeNoAlloc` //! //! If you need lower level control than provided by this module, you can operate directly on the //! results of `std.zig.Zoir` directly. This module is a good example of how that can be done. @@ -69,9 +68,7 @@ pub const ParseOptions = @import("zon/parse.zig").Options; pub const ParseStatus = @import("zon/parse.zig").Status; pub const parseFromSlice = @import("zon/parse.zig").parseFromSlice; pub const parseFromZoir = @import("zon/parse.zig").parseFromZoir; -pub const parseFromZoirNoAlloc = @import("zon/parse.zig").parseFromZoirNoAlloc; pub const parseFromZoirNode = @import("zon/parse.zig").parseFromZoirNode; -pub const parseFromZoirNodeNoAlloc = @import("zon/parse.zig").parseFromZoirNodeNoAlloc; pub const parseFree = @import("zon/parse.zig").parseFree; pub const StringifierOptions = @import("zon/stringify.zig").StringifierOptions; diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 82d615d67cef..ceb5e311c275 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -27,7 +27,7 @@ pub const Options = struct { pub const Error = union(enum) { zoir: Zoir.CompileError, - type_check: TypeCheckFailure, + type_check: Error.TypeCheckFailure, pub const Note = union(enum) { zoir: Zoir.CompileError.Note, @@ -109,11 +109,16 @@ pub const Error = union(enum) { }; const TypeCheckFailure = struct { + message: []const u8, + owned: bool, token: Ast.TokenIndex, - detail: union(enum) { - msg: []const u8, - str_lit_err: StrLitErr, - }, + offset: u32, + + fn deinit(self: @This(), gpa: Allocator) void { + if (self.owned) { + gpa.free(self.message); + } + } }; const FormatMessage = struct { @@ -129,31 +134,10 @@ pub const Error = union(enum) { ) !void { _ = f; _ = options; - return switch (self.err) { + switch (self.err) { .zoir => |err| try writer.writeAll(err.msg.get(self.status.zoir.?)), - .type_check => |tc| switch (tc.detail) { - .msg => |msg| try writer.writeAll(msg), - .str_lit_err => |str_lit_err| { - const raw_string = self.status.ast.?.tokenSlice(tc.token); - return str_lit_err.lower( - raw_string, - 0, - struct { - fn lowerStrLitErr( - w: @TypeOf(writer), - offset: u32, - comptime format: []const u8, - args: anytype, - ) @TypeOf(w).Error!void { - _ = offset; - try w.print(format, args); - } - }.lowerStrLitErr, - .{writer}, - ); - }, - }, - }; + .type_check => |tc| try writer.writeAll(tc.message), + } } pub fn fmtMessage(self: @This(), status: *const Status) std.fmt.Formatter(formatMessage) { @@ -171,13 +155,7 @@ pub const Error = union(enum) { err.token, err.node_or_offset, ), - .type_check => |err| { - const offset = switch (err.detail) { - .msg => 0, - .str_lit_err => |sle| sle.offset(), - }; - return ast.tokenLocation(@intCast(offset), err.token); - }, + .type_check => |err| return ast.tokenLocation(err.offset, err.token), }; } @@ -214,6 +192,7 @@ pub const Status = struct { 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; } @@ -239,7 +218,11 @@ pub const Status = struct { 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 }); + try writer.print("{}:{}: note: {s}\n", .{ + note_loc.line + 1, + note_loc.column + 1, + note_msg, + }); } } } @@ -340,7 +323,10 @@ pub fn parseFromSlice( } test "std.zon parseFromSlice syntax error" { - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, std.testing.allocator, ".{", null, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(u8, std.testing.allocator, ".{", null, .{}), + ); } /// Like `parseFromSlice`, but operates on `Zoir` instead of ZON source. @@ -355,29 +341,6 @@ pub fn parseFromZoir( return parseFromZoirNode(T, gpa, ast, zoir, .root, status, options); } -/// Like `parseFromZoir`, but does not take an allocator. -/// -/// Asserts at comptime that no values of `T` require dynamic allocation. -pub fn parseFromZoirNoAlloc( - comptime T: type, - ast: Ast, - zoir: Zoir, - status: ?*Status, - comptime options: Options, -) error{ParseZon}!T { - return parseFromZoirNodeNoAlloc(T, ast, zoir, .root, status, options); -} - -test "std.zon parseFromZoirNoAlloc" { - var ast = try std.zig.Ast.parse(std.testing.allocator, ".{ .x = 1.5, .y = 2.5 }", .zon); - defer ast.deinit(std.testing.allocator); - var zoir = try ZonGen.generate(std.testing.allocator, ast, false); - defer zoir.deinit(std.testing.allocator); - const S = struct { x: f32, y: f32 }; - const found = try parseFromZoirNoAlloc(S, ast, zoir, null, .{}); - try std.testing.expectEqual(S{ .x = 1.5, .y = 2.5 }, found); -} - /// Like `parseFromZoir`, but the parse starts on `node` instead of root. pub fn parseFromZoirNode( comptime T: type, @@ -408,43 +371,6 @@ pub fn parseFromZoirNode( return parser.parseExpr(T, options, node); } -/// See `parseFromZoirNoAlloc` and `parseFromZoirNode`. -pub fn parseFromZoirNodeNoAlloc( - comptime T: type, - ast: Ast, - zoir: Zoir, - node: Zoir.Node.Index, - status: ?*Status, - comptime options: Options, -) error{ParseZon}!T { - if (comptime requiresAllocator(T)) { - @compileError(@typeName(T) ++ ": requires allocator"); - } - var buffer: [0]u8 = .{}; - var fba = std.heap.FixedBufferAllocator.init(&buffer); - return parseFromZoirNode(T, fba.allocator(), ast, zoir, node, status, options) catch |e| switch (e) { - error.OutOfMemory => unreachable, // No allocations - else => |other| return other, - }; -} - -test "std.zon parseFromZoirNode and parseFromZoirNodeNoAlloc" { - const gpa = std.testing.allocator; - - var ast = try std.zig.Ast.parse(gpa, ".{ .vec = .{ .x = 1.5, .y = 2.5 } }", .zon); - defer ast.deinit(gpa); - var zoir = try ZonGen.generate(gpa, ast, false); - defer zoir.deinit(gpa); - - const vec = Zoir.Node.Index.root.get(zoir).struct_literal.vals.at(0); - - const Vec2 = struct { x: f32, y: f32 }; - const parsed = try parseFromZoirNode(Vec2, gpa, ast, zoir, vec, null, .{}); - const parsed_no_alloc = try parseFromZoirNodeNoAlloc(Vec2, ast, zoir, vec, null, .{}); - try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed); - try std.testing.expectEqual(Vec2{ .x = 1.5, .y = 2.5 }, parsed_no_alloc); -} - fn requiresAllocator(comptime T: type) bool { // Keep in sync with parseFree, stringify, and requiresAllocator. return switch (@typeInfo(T)) { @@ -540,7 +466,7 @@ fn parseExpr( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { // Keep in sync with parseFree, stringify, and requiresAllocator. switch (@typeInfo(T)) { .bool => return self.parseBool(node), @@ -565,7 +491,7 @@ fn parseOptional( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { if (node.get(self.zoir) == .null) { return null; } @@ -599,7 +525,7 @@ fn parseUnion( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; @@ -711,8 +637,15 @@ test "std.zon unions" { const Union = union { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.z=2.5}", &status, .{})); - try std.testing.expectFmt("1:4: error: unexpected field, supported fields: x, y\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:4: error: unexpected field, supported fields: x, y\n", + "{}", + .{status}, + ); } // Explicit void field @@ -720,7 +653,10 @@ test "std.zon unions" { const Union = union(enum) { x: void }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x=1}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Union, gpa, ".{.x=1}", &status, .{}), + ); try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status}); } @@ -729,7 +665,10 @@ test "std.zon unions" { const Union = union { x: f32, y: bool }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}), + ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } @@ -738,7 +677,10 @@ test "std.zon unions" { const Union = union { x: f32, y: bool }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".{}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Union, gpa, ".{}", &status, .{}), + ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } @@ -757,7 +699,11 @@ test "std.zon unions" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".y", &status, .{})); - try std.testing.expectFmt("1:2: error: unexpected field, supported fields: x\n", "{}", .{status}); + try std.testing.expectFmt( + "1:2: error: unexpected field, supported fields: x\n", + "{}", + .{status}, + ); } // Non void field for enum literal coercion @@ -775,8 +721,9 @@ fn parseStruct( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { - const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.zoir)) { +) !T { + const repr = node.get(self.zoir); + const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (repr) { .struct_literal => |nodes| nodes, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return self.failExpectedContainer(T, node), @@ -891,7 +838,13 @@ test "std.zon structs" { { const Foo = struct { bar: []const u8, baz: []const []const u8 }; - const parsed = try parseFromSlice(Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", null, .{}); + const parsed = try parseFromSlice( + Foo, + gpa, + ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", + null, + .{}, + ); defer parseFree(gpa, parsed); try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); } @@ -901,8 +854,15 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); - try std.testing.expectFmt("1:12: error: unexpected field, supported fields: x, y\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:12: error: unexpected field, supported fields: x, y\n", + "{}", + .{status}, + ); } // Duplicate field @@ -910,8 +870,11 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{})); - try std.testing.expectFmt("1:12: error: duplicate field\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{}), + ); + try std.testing.expectFmt("1:12: error: duplicate struct field name\n", "{}", .{status}); } // Ignore unknown fields @@ -928,7 +891,10 @@ test "std.zon structs" { const Vec2 = struct {}; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + ); try std.testing.expectFmt("1:4: error: unexpected field, no fields expected\n", "{}", .{status}); } @@ -937,7 +903,10 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}), + ); try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status}); } @@ -1028,7 +997,13 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice(struct { u8, u8, u8 }, gpa, "Tuple{1, 2, 3}", &status, .{}); + const parsed = parseFromSlice( + 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 @@ -1057,7 +1032,7 @@ fn parseTuple( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -1068,10 +1043,11 @@ fn parseTuple( const field_infos = @typeInfo(T).@"struct".fields; if (nodes.len > field_infos.len) { - return self.failNode(nodes.at(field_infos.len), std.fmt.comptimePrint( + 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| { @@ -1081,10 +1057,7 @@ fn parseTuple( const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); @field(result, field_infos[i].name) = typed.*; } else { - return self.failNode(node, std.fmt.comptimePrint( - "missing tuple field with index {}", - .{i}, - )); + return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); } } else { // If we fail to parse this field, free all fields before it @@ -1142,7 +1115,10 @@ test "std.zon tuples" { const Tuple = struct { f32, bool }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}), + ); try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status}); } @@ -1151,8 +1127,15 @@ test "std.zon tuples" { const Tuple = struct { f32, bool }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{0.5}", &status, .{})); - try std.testing.expectFmt("1:2: error: missing tuple field with index 1\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(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 @@ -1160,7 +1143,10 @@ test "std.zon tuples" { const Tuple = struct { f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}), + ); try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status}); } @@ -1169,7 +1155,10 @@ test "std.zon tuples" { const Struct = struct { foo: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, gpa, ".{10.0}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Struct, gpa, ".{10.0}", &status, .{}), + ); try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status}); } @@ -1199,7 +1188,7 @@ fn parseArray( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -1210,17 +1199,15 @@ fn parseArray( // Check if the size matches if (nodes.len > array_info.len) { - return self.failNode(nodes.at(array_info.len), std.fmt.comptimePrint( + return self.failNodeFmt( + nodes.at(array_info.len), "index {} outside of tuple length {}", .{ array_info.len, array_info.len }, - )); + ); } else if (nodes.len < array_info.len) { switch (nodes.len) { inline 0...array_info.len => |n| { - return self.failNode(node, std.fmt.comptimePrint( - "missing tuple field with index {}", - .{n}, - )); + return self.failNodeFmt(node, "missing tuple field with index {}", .{n}); }, else => unreachable, } @@ -1344,7 +1331,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); try std.testing.expectFmt("1:3: error: index 0 outside of tuple length 0\n", "{}", .{status}); } @@ -1352,7 +1342,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), + ); try std.testing.expectFmt("1:8: error: index 1 outside of tuple length 1\n", "{}", .{status}); } @@ -1360,16 +1353,30 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{})); - try std.testing.expectFmt("1:2: error: missing tuple field with index 1\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: missing tuple field with index 1\n", + "{}", + .{status}, + ); } // Expect 3 find 0 { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, ".{}", &status, .{})); - try std.testing.expectFmt("1:2: error: missing tuple field with index 0\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([3]u8, gpa, ".{}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: missing tuple field with index 0\n", + "{}", + .{status}, + ); } // Wrong inner type @@ -1378,7 +1385,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } @@ -1386,7 +1396,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } } @@ -1397,7 +1410,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([3]u8, gpa, "'a'", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([3]u8, gpa, "'a'", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1405,7 +1421,10 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, "'a'", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]u8, gpa, "'a'", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1414,8 +1433,15 @@ test "std.zon arrays and slices" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{})); - try std.testing.expectFmt("1:3: error: pointers are not available in ZON\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}), + ); + try std.testing.expectFmt( + "1:3: error: pointers are not available in ZON\n", + "{}", + .{status}, + ); } } @@ -1424,7 +1450,7 @@ fn parsePointer( comptime T: type, comptime options: Options, node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon }!T { +) !T { switch (node.get(self.zoir)) { .string_literal => return try self.parseString(T, node), .array_literal => |nodes| return try self.parseSlice(T, options, nodes), @@ -1448,13 +1474,9 @@ fn parseString( switch (try ZonGen.parseStrLit(self.ast, ast_node, buf.writer(self.gpa))) { .success => {}, .failure => |err| { - @branchHint(.cold); const token = self.ast.nodes.items(.main_token)[ast_node]; - if (self.status) |s| s.type_check = .{ - .detail = .{ .str_lit_err = err }, - .token = token, - }; - return error.ParseZon; + const raw_string = self.ast.tokenSlice(token); + return self.failTokenFmt(token, @intCast(err.offset()), "{s}", .{err.fmt(raw_string)}); }, } @@ -1479,7 +1501,7 @@ fn parseSlice( comptime T: type, comptime options: Options, nodes: Zoir.Node.Index.Range, -) error{ OutOfMemory, ParseZon }!T { +) !T { const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice @@ -1588,14 +1610,26 @@ test "std.zon string literal" { // Zero terminated slices { { - const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\"abc\"", null, .{}); + const parsed: [:0]const u8 = try parseFromSlice( + [:0]const u8, + gpa, + "\"abc\"", + null, + .{}, + ); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } { - const parsed: [:0]const u8 = try parseFromSlice([:0]const u8, gpa, "\\\\abc", null, .{}); + const parsed: [:0]const u8 = try parseFromSlice( + [:0]const u8, + gpa, + "\\\\abc", + null, + .{}, + ); defer parseFree(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); @@ -1663,14 +1697,20 @@ test "std.zon string literal" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\"a\"", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]const i8, gpa, "\"a\"", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]const i8, gpa, "\\\\a", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]const i8, gpa, "\\\\a", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1680,14 +1720,20 @@ test "std.zon string literal" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } } @@ -1720,14 +1766,21 @@ test "std.zon string literal" { \\} , null, .{}); defer parseFree(gpa, parsed); - try std.testing.expectEqualStrings("hello, world!\nthis is a multiline string!\n\n...", parsed.message); + 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); } } } -fn parseEnumLiteral(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon}!T { +fn parseEnumLiteral( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, +) !T { switch (node.get(self.zoir)) { .enum_literal => |string| { // Create a comptime string map for the enum fields @@ -1760,13 +1813,19 @@ test "std.zon enum literals" { try std.testing.expectEqual(Enum.foo, try parseFromSlice(Enum, gpa, ".foo", null, .{})); try std.testing.expectEqual(Enum.bar, try parseFromSlice(Enum, gpa, ".bar", null, .{})); try std.testing.expectEqual(Enum.baz, try parseFromSlice(Enum, gpa, ".baz", null, .{})); - try std.testing.expectEqual(Enum.@"ab\nc", try parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{})); + try std.testing.expectEqual( + Enum.@"ab\nc", + try parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), + ); // Bad tag { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".qux", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Enum, gpa, ".qux", &status, .{}), + ); try std.testing.expectFmt( "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", "{}", @@ -1778,7 +1837,10 @@ test "std.zon enum literals" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), + ); try std.testing.expectFmt( "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", "{}", @@ -1790,7 +1852,10 @@ test "std.zon enum literals" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Enum, gpa, "true", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(Enum, gpa, "true", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status}); } @@ -1802,32 +1867,83 @@ test "std.zon enum literals" { error.ParseZon, parseFromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), ); - try std.testing.expectFmt("1:2: error: identifier cannot contain null bytes\n", "{}", .{status}); + try std.testing.expectFmt( + "1:2: error: identifier cannot contain null bytes\n", + "{}", + .{status}, + ); } } -fn failToken(self: @This(), token: Ast.TokenIndex, msg: []const u8) error{ParseZon} { +fn failTokenFmt( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, +) error{ OutOfMemory, ParseZon } { @branchHint(.cold); if (self.status) |s| s.type_check = .{ .token = token, - .detail = .{ .msg = msg }, + .offset = offset, + .message = try std.fmt.allocPrint(self.gpa, fmt, args), + .owned = true, }; return error.ParseZon; } -fn failNode(self: @This(), node: Zoir.Node.Index, msg: []const u8) 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.failToken(token, msg); + return self.failTokenFmt(token, 0, fmt, args); } -fn failCannotRepresent(self: @This(), comptime T: type, node: Zoir.Node.Index) error{ParseZon} { +fn failToken( + self: @This(), + failure: Error.TypeCheckFailure, +) error{ParseZon} { @branchHint(.cold); - return self.failNode(node, "type '" ++ @typeName(T) ++ "' cannot represent value"); + if (self.status) |s| s.type_check = failure; + return error.ParseZon; } -fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?usize) error{ParseZon} { +fn failNode( + self: @This(), + node: Zoir.Node.Index, + message: []const u8, +) error{ParseZon} { + @branchHint(.cold); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failToken(.{ + .token = token, + .offset = 0, + .message = message, + .owned = false, + }); +} + +fn failCannotRepresent( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); +} + +fn failUnexpectedField( + self: @This(), + T: type, + node: Zoir.Node.Index, + field: ?usize, +) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = if (field) |f| b: { var buf: [2]Ast.Node.Index = undefined; @@ -1841,22 +1957,30 @@ fn failUnexpectedField(self: @This(), T: type, node: Zoir.Node.Index, field: ?us switch (@typeInfo(T)) { inline .@"struct", .@"union", .@"enum" => |info| { if (info.fields.len == 0) { - return self.failToken(token, "unexpected field, no fields expected"); + return self.failTokenFmt(token, 0, "unexpected field, no fields expected", .{}); } else { - comptime var msg: []const u8 = "unexpected field, supported fields: "; + const msg = "unexpected field, supported fields: "; + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, msg.len * 2); + defer buf.deinit(self.gpa); + const writer = buf.writer(self.gpa); + try writer.writeAll(msg); inline for (info.fields, 0..) |field_info, i| { - if (i != 0) msg = msg ++ ", "; - const id_formatter = comptime std.zig.fmtId(field_info.name); - msg = msg ++ std.fmt.comptimePrint("{}", .{id_formatter}); + if (i != 0) try writer.writeAll(", "); + try writer.print("{}", .{std.zig.fmtId(field_info.name)}); } - return self.failToken(token, msg); + return self.failToken(.{ + .token = token, + .offset = 0, + .message = try buf.toOwnedSlice(self.gpa), + .owned = true, + }); } }, else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), } } -fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ParseZon} { +fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ OutOfMemory, ParseZon } { @branchHint(.cold); switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { @@ -1883,24 +2007,40 @@ fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{Pa @compileError("unreachable, should not be called for type " ++ @typeName(T)); } -fn failMissingField(self: @This(), comptime name: []const u8, node: Zoir.Node.Index) error{ParseZon} { +fn failMissingField( + self: @This(), + comptime name: []const u8, + node: Zoir.Node.Index, +) error{ OutOfMemory, ParseZon } { @branchHint(.cold); - return self.failNode(node, "missing required field " ++ name); + return self.failNodeFmt( + node, + "missing required field {s}", + .{name}, + ); } -fn failDuplicateField(self: @This(), node: Zoir.Node.Index, field: usize) error{ParseZon} { +fn failDuplicateField( + self: @This(), + node: Zoir.Node.Index, + field: usize, +) error{ OutOfMemory, ParseZon } { @branchHint(.cold); 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[field]; const token = self.ast.firstToken(field_node) - 2; - return self.failToken(token, "duplicate field"); + return self.failTokenFmt(token, 0, "duplicate struct field name", .{}); } // Technically we could do this if we were willing to do a deep equal to verify // the value matched, but doing so doesn't seem to support any real use cases // so isn't worth the complexity at the moment. -fn failRuntimeValueComptimeVar(self: @This(), node: Zoir.Node.Index, field: usize) error{ParseZon} { +fn failRuntimeValueComptimeVar( + self: @This(), + node: Zoir.Node.Index, + field: usize, +) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const ast_node = node.getAstNode(self.zoir); var buf: [2]Ast.Node.Index = undefined; @@ -1912,10 +2052,10 @@ fn failRuntimeValueComptimeVar(self: @This(), node: Zoir.Node.Index, field: usiz const value_node = array_init.ast.elements[field]; break :b self.ast.firstToken(value_node); }; - return self.failToken(token, "cannot store runtime value in compile time variable"); + return self.failTokenFmt(token, 0, "cannot store runtime value in compile time variable", .{}); } -fn parseBool(self: @This(), node: Zoir.Node.Index) error{ParseZon}!bool { +fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { switch (node.get(self.zoir)) { .true => return true, .false => return false, @@ -1934,7 +2074,10 @@ test "std.zon parse bool" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(bool, gpa, " foo", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(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' @@ -1954,7 +2097,7 @@ fn parseInt( self: @This(), comptime T: type, node: Zoir.Node.Index, -) error{ParseZon}!T { +) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return std.math.cast(T, val) orelse @@ -1967,7 +2110,7 @@ fn parseInt( .char_literal => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), - else => return self.failNode(node, "expected type '" ++ @typeName(T) ++ "'"), + else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), } } @@ -1975,7 +2118,7 @@ fn parseFloat( self: @This(), comptime T: type, node: Zoir.Node.Index, -) error{ParseZon}!T { +) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return @floatFromInt(val), @@ -1986,7 +2129,7 @@ fn parseFloat( .neg_inf => return -std.math.inf(T), .nan => return std.math.nan(T), .char_literal => |val| return @floatFromInt(val), - else => return self.failNode(node, "expected type '" ++ @typeName(T) ++ "'"), + else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), } } @@ -2071,14 +2214,34 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i66, gpa, "36893488147419103232", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'i66' cannot represent value\n", "{}", .{status}); + try std.testing.expectError(error.ParseZon, parseFromSlice( + 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, parseFromSlice(i66, gpa, "-36893488147419103233", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'i66' cannot represent value\n", "{}", .{status}); + try std.testing.expectError(error.ParseZon, parseFromSlice( + 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 @@ -2163,7 +2326,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "32a32", &status, .{})); - try std.testing.expectFmt("1:3: error: invalid digit 'a' for decimal base\n", "{}", .{status}); + try std.testing.expectFmt( + "1:3: error: invalid digit 'a' for decimal base\n", + "{}", + .{status}, + ); } // Failing to parse as int @@ -2179,7 +2346,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "256", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); } // Failing because a negative int is out of range @@ -2187,7 +2358,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-129", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'i8' cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: type 'i8' cannot represent value\n", + "{}", + .{status}, + ); } // Failing because an unsigned int is negative @@ -2195,7 +2370,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); } // Failing because a float is non-whole @@ -2203,7 +2382,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "1.5", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); } // Failing because a float is negative @@ -2211,7 +2394,11 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1.0", &status, .{})); - try std.testing.expectFmt("1:1: error: type 'u8' cannot represent value\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: type 'u8' cannot represent value\n", + "{}", + .{status}, + ); } // Negative integer zero @@ -2241,7 +2428,9 @@ test "std.zon parse int" { } // Negative float 0 is allowed - try std.testing.expect(std.math.isNegativeZero(try parseFromSlice(f32, gpa, "-0.0", null, .{}))); + try std.testing.expect( + std.math.isNegativeZero(try parseFromSlice(f32, gpa, "-0.0", null, .{})), + ); try std.testing.expect(std.math.isPositiveZero(try parseFromSlice(f32, gpa, "0.0", null, .{}))); // Double negation is not allowed @@ -2249,14 +2438,25 @@ test "std.zon parse int" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "--2", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{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, parseFromSlice(f32, gpa, "--2.0", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(f32, gpa, "--2.0", &status, .{}), + ); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); } // Invalid int literal @@ -2287,13 +2487,21 @@ test "std.zon negative char" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{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, parseFromSlice(i16, gpa, "-'a'", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); } } @@ -2302,8 +2510,14 @@ test "std.zon parse float" { // Test decimals try std.testing.expectEqual(@as(f16, 0.5), try parseFromSlice(f16, gpa, "0.5", null, .{})); - try std.testing.expectEqual(@as(f32, 123.456), try parseFromSlice(f32, gpa, "123.456", null, .{})); - try std.testing.expectEqual(@as(f64, -123.456), try parseFromSlice(f64, gpa, "-123.456", null, .{})); + try std.testing.expectEqual( + @as(f32, 123.456), + try parseFromSlice(f32, gpa, "123.456", null, .{}), + ); + try std.testing.expectEqual( + @as(f64, -123.456), + try parseFromSlice(f64, gpa, "-123.456", null, .{}), + ); try std.testing.expectEqual(@as(f128, 42.5), try parseFromSlice(f128, gpa, "42.5", null, .{})); // Test whole numbers with and without decimals @@ -2341,11 +2555,20 @@ test "std.zon parse float" { )); // Exponents, underscores - try std.testing.expectEqual(@as(f32, 123.0E+77), try parseFromSlice(f32, gpa, "12_3.0E+77", null, .{})); + try std.testing.expectEqual( + @as(f32, 123.0E+77), + try parseFromSlice(f32, gpa, "12_3.0E+77", null, .{}), + ); // Hexadecimal - try std.testing.expectEqual(@as(f32, 0x103.70p-5), try parseFromSlice(f32, gpa, "0x103.70p-5", null, .{})); - try std.testing.expectEqual(@as(f32, -0x103.70), try parseFromSlice(f32, gpa, "-0x103.70", null, .{})); + try std.testing.expectEqual( + @as(f32, 0x103.70p-5), + try parseFromSlice(f32, gpa, "0x103.70p-5", null, .{}), + ); + try std.testing.expectEqual( + @as(f32, -0x103.70), + try parseFromSlice(f32, gpa, "-0x103.70", null, .{}), + ); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), try parseFromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), @@ -2361,7 +2584,11 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-nan", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{status}); + try std.testing.expectFmt( + "1:1: error: expected number or 'inf' after '-'\n", + "{}", + .{status}, + ); } // nan as int not allowed @@ -2413,14 +2640,21 @@ test "std.zon parse float" { var status: Status = .{}; defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-foo", &status, .{})); - try std.testing.expectFmt("1:1: error: expected number or 'inf' after '-'\n", "{}", .{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, parseFromSlice(f32, gpa, "\"foo\"", &status, .{})); + try std.testing.expectError( + error.ParseZon, + parseFromSlice(f32, gpa, "\"foo\"", &status, .{}), + ); try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status}); } } @@ -2473,24 +2707,34 @@ test "std.zon free on error" { // Test freeing partially allocated arrays { - try std.testing.expectError(error.ParseZon, parseFromSlice([3][]const u8, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice( + [3][]const u8, + std.testing.allocator, \\.{ \\ "hello", \\ false, \\ false, \\} - , null, .{})); + , + null, + .{}, + )); } // Test freeing partially allocated slices { - try std.testing.expectError(error.ParseZon, parseFromSlice([][]const u8, std.testing.allocator, + try std.testing.expectError(error.ParseZon, parseFromSlice( + [][]const u8, + std.testing.allocator, \\.{ \\ "hello", \\ "world", \\ false, \\} - , null, .{})); + , + null, + .{}, + )); } // We can parse types that can't be freed, as long as they contain no allocations, e.g. untagged @@ -2522,9 +2766,13 @@ test "std.zon free on error" { union { x: []const u8 }, bool, }; - const result = try parseFromSlice(S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", null, .{ - .free_on_error = false, - }); + const result = try parseFromSlice( + S, + std.testing.allocator, + ".{ .{ .x = \"foo\" }, true }", + null, + .{ .free_on_error = false }, + ); defer parseFree(std.testing.allocator, result[0].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expect(result[1]); From 78ed0a6d63f787a1a2fd1e071e69bcc23092e784 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 19:00:48 -0800 Subject: [PATCH 43/51] Cleans up string parsing API --- lib/std/zig/ZonGen.zig | 16 ++++++++++++---- lib/std/zon/parse.zig | 4 ++-- src/Zcu.zig | 2 +- src/fmt.zig | 4 ++-- src/main.zig | 2 +- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 8fcd3d8cec79..d2a17f263778 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -3,7 +3,7 @@ gpa: Allocator, tree: Ast, -parse_str_lits: bool, +options: Options, nodes: std.MultiArrayList(Zoir.Node.Repr), extra: std.ArrayListUnmanaged(u32), @@ -14,13 +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, parse_str_lits: bool) 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, - .parse_str_lits = parse_str_lits, + .options = options, .nodes = .empty, .extra = .empty, .limbs = .empty, @@ -536,7 +544,7 @@ const StringLiteralResult = union(enum) { }; fn strLitAsString(zg: *ZonGen, str_node: Ast.Node.Index) !StringLiteralResult { - if (!zg.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } }; + if (!zg.options.parse_str_lits) return .{ .slice = .{ .start = 0, .len = 0 } }; const gpa = zg.gpa; const string_bytes = &zg.string_bytes; diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index ceb5e311c275..d80e2567e7d0 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -313,7 +313,7 @@ pub fn parseFromSlice( defer if (status == null) ast.deinit(gpa); if (status) |s| s.ast = ast; - var zoir = try ZonGen.generate(gpa, ast, false); + var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer if (status == null) zoir.deinit(gpa); if (status) |s| s.zoir = zoir; if (zoir.hasCompileErrors()) return error.ParseZon; @@ -1585,7 +1585,7 @@ test "std.zon string literal" { { var ast = try std.zig.Ast.parse(gpa, "\"abcd\"", .zon); defer ast.deinit(gpa); - var zoir = try ZonGen.generate(gpa, ast, false); + var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer zoir.deinit(gpa); var status: Status = .{}; defer status.deinit(gpa); diff --git a/src/Zcu.zig b/src/Zcu.zig index 95640a75f785..90f8440e0738 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -791,7 +791,7 @@ pub const File = struct { if (file.zoir) |*zoir| return zoir; assert(file.tree_loaded); assert(file.tree.mode == .zon); - file.zoir = try ZonGen.generate(gpa, file.tree, true); + file.zoir = try ZonGen.generate(gpa, file.tree, .{}); return &file.zoir.?; } diff --git a/src/fmt.zig b/src/fmt.zig index 8b79efbafcb9..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, true); + 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, true); + 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 c765b874faac..ddc04f60df2e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6240,7 +6240,7 @@ fn cmdAstCheck( } }, .zon => { - const zoir = try ZonGen.generate(gpa, file.tree, true); + const zoir = try ZonGen.generate(gpa, file.tree, .{}); defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { From 288981014399a0fff1ad741d7ba528fcade858da Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 19:16:57 -0800 Subject: [PATCH 44/51] Removes dead code, fixes expect order --- src/zon.zig | 47 ------------------------------------------- test/behavior/zon.zig | 20 +++++++++--------- 2 files changed, 10 insertions(+), 57 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 976737afc228..63fdccf59b1f 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -91,53 +91,6 @@ const Ident = struct { } }; -const FieldTypes = union(enum) { - st: struct { - ty: Type, - loaded: InternPool.LoadedStructType, - }, - un: struct { - ty: Type, - loaded: InternPool.LoadedEnumType, - }, - none, - - fn init(ty: ?Type, sema: *Sema) !@This() { - const t = ty orelse return .none; - const ip = &sema.pt.zcu.intern_pool; - switch (t.zigTypeTagOrPoison(sema.pt.zcu) catch return .none) { - .@"struct" => { - try t.resolveFully(sema.pt); - const loaded_struct_type = ip.loadStructType(t.toIntern()); - return .{ .st = .{ - .ty = t, - .loaded = loaded_struct_type, - } }; - }, - .@"union" => { - try t.resolveFully(sema.pt); - const loaded_union_type = ip.loadUnionType(t.toIntern()); - const loaded_tag_type = loaded_union_type.loadTagType(ip); - return .{ .un = .{ - .ty = t, - .loaded = loaded_tag_type, - } }; - }, - else => return .none, - } - } - - fn get(self: *const @This(), name: NullTerminatedString, zcu: *Zcu) ?Type { - const ip = &zcu.intern_pool; - const self_ty, const index = switch (self.*) { - .st => |st| .{ st.ty, st.loaded.nameIndex(ip, name) orelse return null }, - .un => |un| .{ un.ty, un.loaded.nameIndex(ip, name) orelse return null }, - .none => return null, - }; - return self_ty.fieldType(index, zcu); - } -}; - fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { .bool => return self.lowerBool(node), diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 0f268cacd40a..123d2b180f24 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -33,9 +33,9 @@ test "union" { const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - try expectEqual(union1.x, 1.5); - try expectEqual(union2.y, true); - try expectEqual(union3.z, {}); + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); } // Inferred tag @@ -50,9 +50,9 @@ test "union" { const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - try expectEqual(union1.x, 1.5); - try expectEqual(union2.y, true); - try expectEqual(union3.z, {}); + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); } // Explicit tag @@ -72,9 +72,9 @@ test "union" { const union2: Union = @import("zon/union2.zon"); const union3: Union = @import("zon/union3.zon"); - try expectEqual(union1.x, 1.5); - try expectEqual(union2.y, true); - try expectEqual(union3.z, {}); + try expectEqual(1.5, union1.x); + try expectEqual(true, union2.y); + try expectEqual({}, union3.z); } } @@ -387,7 +387,7 @@ test "floats" { 0x1234_5678.9ABC_CDEFp-10, }; const actual: T = @import("zon/floats.zon"); - try expectEqual(actual, expected); + try expectEqual(expected, actual); } test "inf and nan" { From fc89256372fa330e2432bdccf52afb9613ebe92c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 20:43:32 -0800 Subject: [PATCH 45/51] Reports errors to sema instead of failed files when appropriate, stops resolving types fully --- src/Zcu.zig | 9 +++++++-- src/zon.zig | 19 ++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Zcu.zig b/src/Zcu.zig index 90f8440e0738..2f6bffd51796 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -787,11 +787,16 @@ pub const File = struct { return &file.tree; } - pub fn getZoir(file: *File, gpa: Allocator) !*const Zoir { + 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(gpa, file.tree, .{}); + 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.?; } diff --git a/src/zon.zig b/src/zon.zig index 63fdccf59b1f..0b61cf15de83 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -35,14 +35,7 @@ pub fn lower( import_loc: LazySrcLoc, block: *Sema.Block, ) CompileError!InternPool.Index { - assert(file.tree_loaded); - const zoir = try file.getZoir(sema.gpa); - - if (zoir.hasCompileErrors()) { - try sema.pt.zcu.failed_files.putNoClobber(sema.gpa, file, null); - return error.AnalysisFail; - } - + _ = try file.getZoir(sema.pt.zcu); const lower_zon: LowerZon = .{ .sema = sema, .file = file, @@ -70,13 +63,12 @@ fn fail( loc: LazySrcLoc.Offset, comptime format: []const u8, args: anytype, -) (Allocator.Error || error{AnalysisFail}) { +) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); const src_loc = try self.lazySrcLoc(loc); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); - try self.sema.pt.zcu.failed_files.putNoClobber(self.sema.pt.zcu.gpa, self.file, err_msg); - return error.AnalysisFail; + return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } const Ident = struct { @@ -568,7 +560,8 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; - try res_ty.resolveFully(self.sema.pt); + 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: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.file.zoir.?)) { @@ -752,7 +745,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; - try res_ty.resolveFully(self.sema.pt); + 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); From 647f3a7a4ec8802937a4df28d1b686593dde64c5 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 21:29:14 -0800 Subject: [PATCH 46/51] Uses sema arena when appropriate --- src/zon.zig | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/zon.zig b/src/zon.zig index 0b61cf15de83..c6bc617ed89d 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -208,8 +208,7 @@ fn lowerInt( } // Create a rational representation of the float - var rational = try std.math.big.Rational.init(gpa); - defer rational.deinit(); + 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, @@ -384,8 +383,6 @@ fn lowerNull(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { } fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { - const gpa = self.sema.gpa; - 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, @@ -405,11 +402,10 @@ fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I ); } - const elems = try gpa.alloc( + const elems = try self.sema.arena.alloc( InternPool.Index, nodes.len + @intFromBool(array_info.sentinel != null), ); - defer gpa.free(elems); for (0..nodes.len) |i| { elems[i] = try self.lowerExpr(nodes.at(@intCast(i)), array_info.elem_type); @@ -490,7 +486,6 @@ fn lowerStructOrTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !Inte fn lowerTuple(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 tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; @@ -506,8 +501,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I const field_defaults = tuple_info.values.get(ip); const field_types = tuple_info.types.get(ip); - const elems = try gpa.alloc(InternPool.Index, field_types.len); - defer gpa.free(elems); + const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len); for (elems) |*v| v.* = .none; for (0..elem_nodes.len) |i| { @@ -575,8 +569,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. }; const field_defaults = struct_info.field_inits.get(ip); - const field_values = try gpa.alloc(InternPool.Index, struct_info.field_names.len); - defer gpa.free(field_values); + const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); for (field_values) |*v| v.* = .none; for (0..fields.names.len) |i| { @@ -688,8 +681,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool ), }; - const elems = try gpa.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); - defer gpa.free(elems); + const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); for (0..elem_nodes.len) |i| { elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), Type.fromInterned(ptr_info.child)); From 59239133037831c587a71965e4bb942521d988f6 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 22:34:44 -0800 Subject: [PATCH 47/51] Cleans up API naming & docs --- lib/std/zon.zig | 57 +--- lib/std/zon/parse.zig | 507 +++++++++++++++++------------------ lib/std/zon/stringify.zig | 536 +++++++++++++++++++++++++------------- 3 files changed, 613 insertions(+), 487 deletions(-) diff --git a/lib/std/zon.zig b/lib/std/zon.zig index 0ade6ae9552c..3e53a5f456ee 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -1,8 +1,7 @@ -//! ZON serialization and deserialization. +//! ZON parsing and stringification. //! -//! # ZON -//! ZON, or Zig Object Notation, is a subset* of Zig used for data storage. ZON contains no type -//! names. +//! 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 @@ -32,54 +31,10 @@ //! "This string is a valid ZON object." //! ``` //! -//! * ZON is not currently a true subset of Zig, because it supports `nan` and -//! `inf` literals, which Zig does not. -//! -//! # Deserialization -//! -//! The simplest way to deserialize ZON at runtime is `parseFromSlice`. (For parsing ZON at -//! comptime, you can use `@import`.) -//! -//! Parsing from individual Zoir nodes is also available: -//! * `parseFromZoir` -//! * `parseFromZoirNode` -//! * `parseFromZoirNode` -//! -//! If you need lower level control than provided by this module, you can operate directly on the -//! results of `std.zig.Zoir` directly. This module is a good example of how that can be done. -//! -//! -//! # Serialization -//! -//! The simplest way to serialize to ZON is to call `stringify`. -//! -//! If you need to serialize recursive types, the following functions are also provided: -//! * `stringifyMaxDepth` -//! * `stringifyArbitraryDepth` -//! -//! If you need more control over the serialization process, for example to control which fields are -//! serialized, configure fields individually, or to stringify ZON values that do not exist in -//! memory, you can use `Stringifier`. -//! -//! Note that serializing floats with more than 64 bits may result in a loss of precision -//! (see https://github.com/ziglang/zig/issues/1181). - -pub const ParseOptions = @import("zon/parse.zig").Options; -pub const ParseStatus = @import("zon/parse.zig").Status; -pub const parseFromSlice = @import("zon/parse.zig").parseFromSlice; -pub const parseFromZoir = @import("zon/parse.zig").parseFromZoir; -pub const parseFromZoirNode = @import("zon/parse.zig").parseFromZoirNode; -pub const parseFree = @import("zon/parse.zig").parseFree; +//! ZON may not contain type names. -pub const StringifierOptions = @import("zon/stringify.zig").StringifierOptions; -pub const StringifyValueOptions = @import("zon/stringify.zig").StringifyValueOptions; -pub const StringifyOptions = @import("zon/stringify.zig").StringifyOptions; -pub const StringifyContainerOptions = @import("zon/stringify.zig").StringifyContainerOptions; -pub const Stringifier = @import("zon/stringify.zig").Stringifier; -pub const stringify = @import("zon/stringify.zig").stringify; -pub const stringifyMaxDepth = @import("zon/stringify.zig").stringifyMaxDepth; -pub const stringifyArbitraryDepth = @import("zon/stringify.zig").stringifyArbitraryDepth; -pub const stringifier = @import("zon/stringify.zig").stringifier; +pub const parse = @import("zon/parse.zig"); +pub const stringify = @import("zon/stringify.zig"); test { _ = @import("zon/parse.zig"); diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index d80e2567e7d0..c4a4c097b097 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1,3 +1,12 @@ +//! The simplest way to parse ZON at runtime is to use `fromSlice`. If you need to parse ZON at +//! compile time, you may use `@import`. +//! +//! Parsing from individual Zoir nodes is also available: +//! * `fromZoir` +//! * `fromZoirNode` +//! +//! For lower level control, it is possible to operate on `std.zig.Zoir` directly. + const std = @import("std"); const Allocator = std.mem.Allocator; const Ast = std.zig.Ast; @@ -235,7 +244,7 @@ test "std.zon ast errors" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), + fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), ); try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status}); } @@ -243,7 +252,7 @@ test "std.zon ast errors" { test "std.zon comments" { const gpa = std.testing.allocator; - try std.testing.expectEqual(@as(u8, 10), parseFromSlice(u8, gpa, + try std.testing.expectEqual(@as(u8, 10), fromSlice(u8, gpa, \\// comment \\10 // comment \\// comment @@ -252,7 +261,7 @@ test "std.zon comments" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, \\//! comment \\10 // comment \\// comment @@ -273,7 +282,7 @@ test "std.zon failure/oom formatting" { }); var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.OutOfMemory, parseFromSlice( + try std.testing.expectError(error.OutOfMemory, fromSlice( []const u8, failing_allocator.allocator(), "\"foo\"", @@ -290,7 +299,7 @@ test "std.zon failure/oom formatting" { /// /// 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 parseFromSlice( +pub fn fromSlice( /// The type to deserialize into. May only transitively contain the following supported types: /// * bools /// * fixed sized numeric types @@ -319,18 +328,18 @@ pub fn parseFromSlice( if (zoir.hasCompileErrors()) return error.ParseZon; if (status) |s| s.* = .{}; - return parseFromZoir(T, gpa, ast, zoir, status, options); + return fromZoir(T, gpa, ast, zoir, status, options); } -test "std.zon parseFromSlice syntax error" { +test "std.zon fromSlice syntax error" { try std.testing.expectError( error.ParseZon, - parseFromSlice(u8, std.testing.allocator, ".{", null, .{}), + fromSlice(u8, std.testing.allocator, ".{", null, .{}), ); } -/// Like `parseFromSlice`, but operates on `Zoir` instead of ZON source. -pub fn parseFromZoir( +/// Like `fromSlice`, but operates on `Zoir` instead of ZON source. +pub fn fromZoir( comptime T: type, gpa: Allocator, ast: Ast, @@ -338,11 +347,11 @@ pub fn parseFromZoir( status: ?*Status, comptime options: Options, ) error{ OutOfMemory, ParseZon }!T { - return parseFromZoirNode(T, gpa, ast, zoir, .root, status, options); + return fromZoirNode(T, gpa, ast, zoir, .root, status, options); } -/// Like `parseFromZoir`, but the parse starts on `node` instead of root. -pub fn parseFromZoirNode( +/// Like `fromZoir`, but the parse starts on `node` instead of root. +pub fn fromZoirNode( comptime T: type, gpa: Allocator, ast: Ast, @@ -372,7 +381,7 @@ pub fn parseFromZoirNode( } fn requiresAllocator(comptime T: type) bool { - // Keep in sync with parseFree, stringify, and requiresAllocator. + // Keep in sync with free, stringify, and requiresAllocator. return switch (@typeInfo(T)) { .pointer => true, .array => |array| requiresAllocator(array.child), @@ -420,44 +429,44 @@ test "std.zon requiresAllocator" { /// /// 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 parseFree(gpa: Allocator, value: anytype) void { +pub fn free(gpa: Allocator, value: anytype) void { const Value = @TypeOf(value); - // Keep in sync with parseFree, stringify, and requiresAllocator. + // Keep in sync with free, stringify, and requiresAllocator. switch (@typeInfo(Value)) { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { switch (pointer.size) { .One, .Many, .C => if (comptime requiresAllocator(Value)) { - @compileError(@typeName(Value) ++ ": parseFree cannot free non slice pointers"); + @compileError(@typeName(Value) ++ ": free cannot free non slice pointers"); }, .Slice => for (value) |item| { - parseFree(gpa, item); + free(gpa, item); }, } return gpa.free(value); }, .array => for (value) |item| { - parseFree(gpa, item); + free(gpa, item); }, .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { - parseFree(gpa, @field(value, field.name)); + free(gpa, @field(value, field.name)); }, .@"union" => |@"union"| if (@"union".tag_type == null) { if (comptime requiresAllocator(Value)) { - @compileError(@typeName(Value) ++ ": parseFree cannot free untagged unions"); + @compileError(@typeName(Value) ++ ": free cannot free untagged unions"); } } else switch (value) { inline else => |_, tag| { - parseFree(gpa, @field(value, @tagName(tag))); + free(gpa, @field(value, @tagName(tag))); }, }, .optional => if (value) |some| { - parseFree(gpa, some); + free(gpa, some); }, .void => {}, .null => {}, - else => @compileError(@typeName(Value) ++ ": parseFree cannot free this type"), + else => @compileError(@typeName(Value) ++ ": free cannot free this type"), } } @@ -467,7 +476,7 @@ fn parseExpr( comptime options: Options, node: Zoir.Node.Index, ) !T { - // Keep in sync with parseFree, stringify, and requiresAllocator. + // Keep in sync with free, stringify, and requiresAllocator. switch (@typeInfo(T)) { .bool => return self.parseBool(node), .int => return self.parseInt(T, node), @@ -504,18 +513,18 @@ test "std.zon optional" { // Basic usage { - const none = try parseFromSlice(?u32, gpa, "null", null, .{}); + const none = try fromSlice(?u32, gpa, "null", null, .{}); try std.testing.expect(none == null); - const some = try parseFromSlice(?u32, gpa, "1", null, .{}); + const some = try fromSlice(?u32, gpa, "1", null, .{}); try std.testing.expect(some.? == 1); } // Deep free { - const none = try parseFromSlice(?[]const u8, gpa, "null", null, .{}); + const none = try fromSlice(?[]const u8, gpa, "null", null, .{}); try std.testing.expect(none == null); - const some = try parseFromSlice(?[]const u8, gpa, "\"foo\"", null, .{}); - defer parseFree(gpa, some); + const some = try fromSlice(?[]const u8, gpa, "\"foo\"", null, .{}); + defer free(gpa, some); try std.testing.expectEqualStrings("foo", some.?); } } @@ -605,18 +614,18 @@ test "std.zon 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 parseFromSlice(Tagged, gpa, ".{.x = 1.5}", null, .{}); + 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 parseFromSlice(Tagged, gpa, ".{.@\"y y\" = true}", null, .{}); + 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 parseFromSlice(Tagged, gpa, ".z", null, .{}); + 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 parseFromSlice(Tagged, gpa, ".@\"z z\"", null, .{}); + 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 parseFromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); + const untagged_x = try fromSlice(Untagged, gpa, ".{.x = 1.5}", null, .{}); try std.testing.expect(untagged_x.x == 1.5); - const untagged_y = try parseFromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); + const untagged_y = try fromSlice(Untagged, gpa, ".{.@\"y y\" = true}", null, .{}); try std.testing.expect(untagged_y.@"y y"); } @@ -624,11 +633,11 @@ test "std.zon unions" { { const Union = union(enum) { bar: []const u8, baz: bool }; - const noalloc = try parseFromSlice(Union, gpa, ".{.baz = false}", null, .{}); + const noalloc = try fromSlice(Union, gpa, ".{.baz = false}", null, .{}); try std.testing.expectEqual(Union{ .baz = false }, noalloc); - const alloc = try parseFromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{}); - defer parseFree(gpa, alloc); + const alloc = try fromSlice(Union, gpa, ".{.bar = \"qux\"}", null, .{}); + defer free(gpa, alloc); try std.testing.expectEqualDeep(Union{ .bar = "qux" }, alloc); } @@ -639,7 +648,7 @@ test "std.zon unions" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), + fromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), ); try std.testing.expectFmt( "1:4: error: unexpected field, supported fields: x, y\n", @@ -655,7 +664,7 @@ test "std.zon unions" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Union, gpa, ".{.x=1}", &status, .{}), + fromSlice(Union, gpa, ".{.x=1}", &status, .{}), ); try std.testing.expectFmt("1:6: error: expected type 'void'\n", "{}", .{status}); } @@ -667,7 +676,7 @@ test "std.zon unions" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}), + fromSlice(Union, gpa, ".{.x = 1.5, .y = true}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } @@ -679,7 +688,7 @@ test "std.zon unions" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Union, gpa, ".{}", &status, .{}), + fromSlice(Union, gpa, ".{}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } @@ -689,7 +698,7 @@ test "std.zon unions" { const Union = union { x: void }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } @@ -698,7 +707,7 @@ test "std.zon unions" { const Union = union(enum) { x: void }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".y", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".y", &status, .{})); try std.testing.expectFmt( "1:2: error: unexpected field, supported fields: x\n", "{}", @@ -711,7 +720,7 @@ test "std.zon unions" { const Union = union(enum) { x: f32 }; var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(Union, gpa, ".x", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".x", &status, .{})); try std.testing.expectFmt("1:2: error: expected union\n", "{}", .{status}); } } @@ -751,7 +760,7 @@ fn parseStruct( 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; - parseFree(self.gpa, @field(result, name)); + free(self.gpa, @field(result, name)); }, else => unreachable, // Can't be out of bounds } @@ -821,16 +830,16 @@ test "std.zon structs" { const Vec2 = struct { x: f32, y: f32 }; const Vec3 = struct { x: f32, y: f32, z: f32 }; - const zero = try parseFromSlice(Vec0, gpa, ".{}", null, .{}); + const zero = try fromSlice(Vec0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Vec0{}, zero); - const one = try parseFromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); + const one = try fromSlice(Vec1, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec1{ .x = 1.2 }, one); - const two = try parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 3.4}", null, .{}); + 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 parseFromSlice(Vec3, gpa, ".{.x = 1.2, .y = 3.4, .z = 5.6}", null, .{}); + 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); } @@ -838,14 +847,14 @@ test "std.zon structs" { { const Foo = struct { bar: []const u8, baz: []const []const u8 }; - const parsed = try parseFromSlice( + const parsed = try fromSlice( Foo, gpa, ".{.bar = \"qux\", .baz = .{\"a\", \"b\"}}", null, .{}, ); - defer parseFree(gpa, parsed); + defer free(gpa, parsed); try std.testing.expectEqualDeep(Foo{ .bar = "qux", .baz = &.{ "a", "b" } }, parsed); } @@ -856,7 +865,7 @@ test "std.zon structs" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); try std.testing.expectFmt( "1:12: error: unexpected field, supported fields: x, y\n", @@ -872,7 +881,7 @@ test "std.zon structs" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{}), + fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{}), ); try std.testing.expectFmt("1:12: error: duplicate struct field name\n", "{}", .{status}); } @@ -880,7 +889,7 @@ test "std.zon structs" { // Ignore unknown fields { const Vec2 = struct { x: f32, y: f32 = 2.0 }; - const parsed = try parseFromSlice(Vec2, gpa, ".{ .x = 1.0, .z = 3.0 }", null, .{ + 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); @@ -893,7 +902,7 @@ test "std.zon structs" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), + fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); try std.testing.expectFmt("1:4: error: unexpected field, no fields expected\n", "{}", .{status}); } @@ -905,7 +914,7 @@ test "std.zon structs" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}), + fromSlice(Vec2, gpa, ".{.x=1.5}", &status, .{}), ); try std.testing.expectFmt("1:2: error: missing required field y\n", "{}", .{status}); } @@ -913,14 +922,14 @@ test "std.zon structs" { // Default field { const Vec2 = struct { x: f32, y: f32 = 1.5 }; - const parsed = try parseFromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + 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 parseFromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); + const parsed = try fromSlice(Vec2, gpa, ".{.x = 1.2}", null, .{}); try std.testing.expectEqual(Vec2{ .x = 1.2, .y = 1.5 }, parsed); } @@ -929,7 +938,7 @@ test "std.zon structs" { const Vec2 = struct { x: f32, comptime y: f32 = 1.5 }; var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{}); + const parsed = fromSlice(Vec2, gpa, ".{.x = 1.2, .y = 1.5}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:18: error: cannot store runtime value in compile time variable @@ -941,14 +950,14 @@ test "std.zon structs" { // incorrect way that broke for enum values) { const Vec0 = struct { x: enum { x } }; - const parsed = try parseFromSlice(Vec0, gpa, ".{ .x = .x }", null, .{}); + 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 parseFromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); + const parsed = try fromSlice(Vec0, gpa, ".{ .@\"x x\" = .@\"x x\" }", null, .{}); try std.testing.expectEqual(Vec0{ .@"x x" = .@"x x" }, parsed); } @@ -958,7 +967,7 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice(struct {}, gpa, "Empty{}", &status, .{}); + 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 @@ -971,7 +980,7 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice([3]u8, gpa, "[3]u8{1, 2, 3}", &status, .{}); + 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 @@ -984,7 +993,7 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice([]u8, gpa, "[]u8{1, 2, 3}", &status, .{}); + 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 @@ -997,7 +1006,7 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice( + const parsed = fromSlice( struct { u8, u8, u8 }, gpa, "Tuple{1, 2, 3}", @@ -1016,7 +1025,7 @@ test "std.zon structs" { { var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice(struct {}, gpa, ".{ .x = Tuple{1, 2, 3} }", &status, .{}); + 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 @@ -1064,7 +1073,7 @@ fn parseTuple( errdefer if (options.free_on_error) { inline for (0..i) |j| { if (j >= i) break; - parseFree(self.gpa, result[j]); + free(self.gpa, result[j]); } }; @@ -1089,24 +1098,24 @@ test "std.zon tuples" { const Tuple2 = struct { f32, bool }; const Tuple3 = struct { f32, bool, u8 }; - const zero = try parseFromSlice(Tuple0, gpa, ".{}", null, .{}); + const zero = try fromSlice(Tuple0, gpa, ".{}", null, .{}); try std.testing.expectEqual(Tuple0{}, zero); - const one = try parseFromSlice(Tuple1, gpa, ".{1.2}", null, .{}); + const one = try fromSlice(Tuple1, gpa, ".{1.2}", null, .{}); try std.testing.expectEqual(Tuple1{1.2}, one); - const two = try parseFromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); + const two = try fromSlice(Tuple2, gpa, ".{1.2, true}", null, .{}); try std.testing.expectEqual(Tuple2{ 1.2, true }, two); - const three = try parseFromSlice(Tuple3, gpa, ".{1.2, false, 3}", null, .{}); + 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 parseFromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); - defer parseFree(gpa, parsed); + const parsed = try fromSlice(Tuple, gpa, ".{\"hello\", \"world\"}", null, .{}); + defer free(gpa, parsed); try std.testing.expectEqualDeep(Tuple{ "hello", "world" }, parsed); } @@ -1117,7 +1126,7 @@ test "std.zon tuples" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}), + fromSlice(Tuple, gpa, ".{0.5, true, 123}", &status, .{}), ); try std.testing.expectFmt("1:14: error: index 2 outside of tuple length 2\n", "{}", .{status}); } @@ -1129,7 +1138,7 @@ test "std.zon tuples" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Tuple, gpa, ".{0.5}", &status, .{}), + fromSlice(Tuple, gpa, ".{0.5}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: missing tuple field with index 1\n", @@ -1145,7 +1154,7 @@ test "std.zon tuples" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}), + fromSlice(Tuple, gpa, ".{.foo = 10.0}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected tuple\n", "{}", .{status}); } @@ -1157,7 +1166,7 @@ test "std.zon tuples" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Struct, gpa, ".{10.0}", &status, .{}), + fromSlice(Struct, gpa, ".{10.0}", &status, .{}), ); try std.testing.expectFmt("1:2: error: expected struct\n", "{}", .{status}); } @@ -1165,7 +1174,7 @@ test "std.zon tuples" { // Comptime field { const Vec2 = struct { f32, comptime f32 = 1.5 }; - const parsed = try parseFromSlice(Vec2, gpa, ".{ 1.2 }", null, .{}); + const parsed = try fromSlice(Vec2, gpa, ".{ 1.2 }", null, .{}); try std.testing.expectEqual(Vec2{ 1.2, 1.5 }, parsed); } @@ -1174,7 +1183,7 @@ test "std.zon tuples" { const Vec2 = struct { f32, comptime f32 = 1.5 }; var status: Status = .{}; defer status.deinit(gpa); - const parsed = parseFromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{}); + const parsed = fromSlice(Vec2, gpa, ".{ 1.2, 1.5}", &status, .{}); try std.testing.expectError(error.ParseZon, parsed); try std.testing.expectFmt( \\1:9: error: cannot store runtime value in compile time variable @@ -1219,7 +1228,7 @@ fn parseArray( // If we fail to parse this field, free all fields before it errdefer if (options.free_on_error) { for (result[0..i]) |item| { - parseFree(self.gpa, item); + free(self.gpa, item); } }; @@ -1239,50 +1248,50 @@ test "std.zon arrays and slices" { { // Arrays { - const zero = try parseFromSlice([0]u8, gpa, ".{}", null, .{}); + const zero = try fromSlice([0]u8, gpa, ".{}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([0]u8, .{}), &zero); - const one = try parseFromSlice([1]u8, gpa, ".{'a'}", null, .{}); + const one = try fromSlice([1]u8, gpa, ".{'a'}", null, .{}); try std.testing.expectEqualSlices(u8, &@as([1]u8, .{'a'}), &one); - const two = try parseFromSlice([2]u8, gpa, ".{'a', 'b'}", null, .{}); + 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 parseFromSlice([2]u8, gpa, ".{'a', 'b',}", null, .{}); + 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 parseFromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + const three = try fromSlice([3]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); try std.testing.expectEqualSlices(u8, &.{ 'a', 'b', 'c' }, &three); - const sentinel = try parseFromSlice([3:'z']u8, gpa, ".{'a', 'b', 'c'}", null, .{}); + 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 parseFromSlice([]const u8, gpa, ".{}", null, .{}); - defer parseFree(gpa, zero); + const zero = try fromSlice([]const u8, gpa, ".{}", null, .{}); + defer free(gpa, zero); try std.testing.expectEqualSlices(u8, @as([]const u8, &.{}), zero); - const one = try parseFromSlice([]u8, gpa, ".{'a'}", null, .{}); - defer parseFree(gpa, one); + const one = try fromSlice([]u8, gpa, ".{'a'}", null, .{}); + defer free(gpa, one); try std.testing.expectEqualSlices(u8, &.{'a'}, one); - const two = try parseFromSlice([]const u8, gpa, ".{'a', 'b'}", null, .{}); - defer parseFree(gpa, two); + 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 parseFromSlice([]const u8, gpa, ".{'a', 'b',}", null, .{}); - defer parseFree(gpa, two_comma); + 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 parseFromSlice([]u8, gpa, ".{'a', 'b', 'c'}", null, .{}); - defer parseFree(gpa, three); + 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 parseFromSlice([:'z']const u8, gpa, ".{'a', 'b', 'c'}", null, .{}); - defer parseFree(gpa, sentinel); + 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); } @@ -1292,16 +1301,16 @@ test "std.zon arrays and slices" { { // Arrays { - const parsed = try parseFromSlice([1][]const u8, gpa, ".{\"abc\"}", null, .{}); - defer parseFree(gpa, parsed); + 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 parseFromSlice([]const []const u8, gpa, ".{\"abc\"}", null, .{}); - defer parseFree(gpa, parsed); + 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); } @@ -1311,7 +1320,7 @@ test "std.zon arrays and slices" { { // Arrays { - const sentinel = try parseFromSlice([1:2]u8, gpa, ".{1}", null, .{}); + 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]); @@ -1319,8 +1328,8 @@ test "std.zon arrays and slices" { // Slice literals { - const sentinel = try parseFromSlice([:2]align(4) u8, gpa, ".{1}", null, .{}); - defer parseFree(gpa, sentinel); + 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]); @@ -1333,7 +1342,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), + fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt("1:3: error: index 0 outside of tuple length 0\n", "{}", .{status}); } @@ -1344,7 +1353,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), + fromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), ); try std.testing.expectFmt("1:8: error: index 1 outside of tuple length 1\n", "{}", .{status}); } @@ -1355,7 +1364,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([2]u8, gpa, ".{'a'}", &status, .{}), + fromSlice([2]u8, gpa, ".{'a'}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: missing tuple field with index 1\n", @@ -1370,7 +1379,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([3]u8, gpa, ".{}", &status, .{}), + fromSlice([3]u8, gpa, ".{}", &status, .{}), ); try std.testing.expectFmt( "1:2: error: missing tuple field with index 0\n", @@ -1387,7 +1396,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + fromSlice([3]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } @@ -1398,7 +1407,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), + fromSlice([]bool, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'bool'\n", "{}", .{status}); } @@ -1412,7 +1421,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([3]u8, gpa, "'a'", &status, .{}), + fromSlice([3]u8, gpa, "'a'", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1423,7 +1432,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]u8, gpa, "'a'", &status, .{}), + fromSlice([]u8, gpa, "'a'", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1435,7 +1444,7 @@ test "std.zon arrays and slices" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}), + fromSlice([]u8, gpa, " &.{'a', 'b', 'c'}", &status, .{}), ); try std.testing.expectFmt( "1:3: error: pointers are not available in ZON\n", @@ -1524,7 +1533,7 @@ fn parseSlice( for (0..nodes.len) |i| { errdefer if (options.free_on_error) { for (slice[0..i]) |item| { - parseFree(self.gpa, item); + free(self.gpa, item); } }; slice[i] = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); @@ -1538,22 +1547,22 @@ test "std.zon string literal" { // Basic string literal { - const parsed = try parseFromSlice([]const u8, gpa, "\"abc\"", null, .{}); - defer parseFree(gpa, parsed); + 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 parseFromSlice([]const u8, gpa, "\"ab\\nc\"", null, .{}); - defer parseFree(gpa, parsed); + 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 parseFromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{}); - defer parseFree(gpa, parsed); + const parsed = try fromSlice([]const u8, gpa, "\"ab\\x00c\"", null, .{}); + defer free(gpa, parsed); try std.testing.expectEqualStrings(@as([]const u8, "ab\x00c"), parsed); } @@ -1564,7 +1573,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]u8, gpa, "\"abcd\"", &status, .{}), + fromSlice([]u8, gpa, "\"abcd\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1574,7 +1583,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]u8, gpa, "\\\\abcd", &status, .{}), + fromSlice([]u8, gpa, "\\\\abcd", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1591,7 +1600,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), + fromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1601,7 +1610,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), + fromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1610,27 +1619,27 @@ test "std.zon string literal" { // Zero terminated slices { { - const parsed: [:0]const u8 = try parseFromSlice( + const parsed: [:0]const u8 = try fromSlice( [:0]const u8, gpa, "\"abc\"", null, .{}, ); - defer parseFree(gpa, parsed); + 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 parseFromSlice( + const parsed: [:0]const u8 = try fromSlice( [:0]const u8, gpa, "\\\\abc", null, .{}, ); - defer parseFree(gpa, parsed); + defer free(gpa, parsed); try std.testing.expectEqualStrings("abc", parsed); try std.testing.expectEqual(@as(u8, 0), parsed[3]); } @@ -1643,7 +1652,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), + fromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1653,7 +1662,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), + fromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1665,7 +1674,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]const u8, gpa, "true", &status, .{}), + fromSlice([]const u8, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected string\n", "{}", .{status}); } @@ -1676,7 +1685,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]const u8, gpa, ".{false}", &status, .{}), + fromSlice([]const u8, gpa, ".{false}", &status, .{}), ); try std.testing.expectFmt("1:3: error: expected type 'u8'\n", "{}", .{status}); } @@ -1687,7 +1696,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), + fromSlice([]const i8, gpa, "\"\\a\"", &status, .{}), ); try std.testing.expectFmt("1:3: error: invalid escape character: 'a'\n", "{}", .{status}); } @@ -1699,7 +1708,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]const i8, gpa, "\"a\"", &status, .{}), + fromSlice([]const i8, gpa, "\"a\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1709,7 +1718,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]const i8, gpa, "\\\\a", &status, .{}), + fromSlice([]const i8, gpa, "\\\\a", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1722,7 +1731,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), + fromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1732,7 +1741,7 @@ test "std.zon string literal" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), + fromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); } @@ -1747,7 +1756,7 @@ test "std.zon string literal" { message2: String, message3: String, }; - const parsed = try parseFromSlice(S, gpa, + const parsed = try fromSlice(S, gpa, \\.{ \\ .message = \\ \\hello, world! @@ -1765,7 +1774,7 @@ test "std.zon string literal" { \\ \\and this. \\} , null, .{}); - defer parseFree(gpa, parsed); + defer free(gpa, parsed); try std.testing.expectEqualStrings( "hello, world!\nthis is a multiline string!\n\n...", parsed.message, @@ -1810,12 +1819,12 @@ test "std.zon enum literals" { }; // Tags that exist - try std.testing.expectEqual(Enum.foo, try parseFromSlice(Enum, gpa, ".foo", null, .{})); - try std.testing.expectEqual(Enum.bar, try parseFromSlice(Enum, gpa, ".bar", null, .{})); - try std.testing.expectEqual(Enum.baz, try parseFromSlice(Enum, gpa, ".baz", null, .{})); + 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 parseFromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), + try fromSlice(Enum, gpa, ".@\"ab\\nc\"", null, .{}), ); // Bad tag @@ -1824,7 +1833,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Enum, gpa, ".qux", &status, .{}), + fromSlice(Enum, gpa, ".qux", &status, .{}), ); try std.testing.expectFmt( "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", @@ -1839,7 +1848,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), + fromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), ); try std.testing.expectFmt( "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", @@ -1854,7 +1863,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Enum, gpa, "true", &status, .{}), + fromSlice(Enum, gpa, "true", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected enum literal\n", "{}", .{status}); } @@ -1865,7 +1874,7 @@ test "std.zon enum literals" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), + fromSlice(Enum, gpa, ".@\"\\x00\"", &status, .{}), ); try std.testing.expectFmt( "1:2: error: identifier cannot contain null bytes\n", @@ -2067,8 +2076,8 @@ test "std.zon parse bool" { const gpa = std.testing.allocator; // Correct floats - try std.testing.expectEqual(true, try parseFromSlice(bool, gpa, "true", null, .{})); - try std.testing.expectEqual(false, try parseFromSlice(bool, gpa, "false", null, .{})); + try std.testing.expectEqual(true, try fromSlice(bool, gpa, "true", null, .{})); + try std.testing.expectEqual(false, try fromSlice(bool, gpa, "false", null, .{})); // Errors { @@ -2076,7 +2085,7 @@ test "std.zon parse bool" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(bool, gpa, " foo", &status, .{}), + fromSlice(bool, gpa, " foo", &status, .{}), ); try std.testing.expectFmt( \\1:2: error: invalid expression @@ -2088,7 +2097,7 @@ test "std.zon parse bool" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(bool, gpa, "123", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(bool, gpa, "123", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'bool'\n", "{}", .{status}); } } @@ -2179,42 +2188,42 @@ test "std.zon parse int" { const gpa = std.testing.allocator; // Test various numbers and types - try std.testing.expectEqual(@as(u8, 10), try parseFromSlice(u8, gpa, "10", null, .{})); - try std.testing.expectEqual(@as(i16, 24), try parseFromSlice(i16, gpa, "24", null, .{})); - try std.testing.expectEqual(@as(i14, -4), try parseFromSlice(i14, gpa, "-4", null, .{})); - try std.testing.expectEqual(@as(i32, -123), try parseFromSlice(i32, gpa, "-123", null, .{})); + 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 parseFromSlice(i8, gpa, "127", null, .{})); - try std.testing.expectEqual(@as(i8, -128), try parseFromSlice(i8, gpa, "-128", null, .{})); + 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 parseFromSlice(u8, gpa, "'a'", null, .{})); - try std.testing.expectEqual(@as(u8, 'z'), try parseFromSlice(u8, gpa, "'z'", null, .{})); + 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 parseFromSlice(u65, gpa, "36893488147419103231", null, .{}), + try fromSlice(u65, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(u65, 36893488147419103231), - try parseFromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), + try fromSlice(u65, gpa, "368934_881_474191032_31", null, .{}), ); // Test big integer limits try std.testing.expectEqual( @as(i66, 36893488147419103231), - try parseFromSlice(i66, gpa, "36893488147419103231", null, .{}), + try fromSlice(i66, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(i66, -36893488147419103232), - try parseFromSlice(i66, gpa, "-36893488147419103232", null, .{}), + try fromSlice(i66, gpa, "-36893488147419103232", null, .{}), ); { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice( + try std.testing.expectError(error.ParseZon, fromSlice( i66, gpa, "36893488147419103232", @@ -2230,7 +2239,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice( + try std.testing.expectError(error.ParseZon, fromSlice( i66, gpa, "-36893488147419103233", @@ -2245,75 +2254,75 @@ test "std.zon parse int" { } // Test parsing whole number floats as integers - try std.testing.expectEqual(@as(i8, -1), try parseFromSlice(i8, gpa, "-1.0", null, .{})); - try std.testing.expectEqual(@as(i8, 123), try parseFromSlice(i8, gpa, "123.0", null, .{})); + 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 parseFromSlice(i16, gpa, "0xff", null, .{})); - try std.testing.expectEqual(@as(i16, -0xff), try parseFromSlice(i16, gpa, "-0xff", null, .{})); - try std.testing.expectEqual(@as(i16, 0o77), try parseFromSlice(i16, gpa, "0o77", null, .{})); - try std.testing.expectEqual(@as(i16, -0o77), try parseFromSlice(i16, gpa, "-0o77", null, .{})); - try std.testing.expectEqual(@as(i16, 0b11), try parseFromSlice(i16, gpa, "0b11", null, .{})); - try std.testing.expectEqual(@as(i16, -0b11), try parseFromSlice(i16, gpa, "-0b11", null, .{})); + 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 parseFromSlice( + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0x1ffffffffffffffff", null, .{}, )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0x1ffffffffffffffff", null, .{}, )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0x1ffffffffffffffff", null, .{}, )); - try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0o3777777777777777777777", null, .{}, )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0o3777777777777777777777", null, .{}, )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0o3777777777777777777777", null, .{}, )); - try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(u65, 0x1ffffffffffffffff), try fromSlice( u65, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", null, .{}, )); - try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, 0x1ffffffffffffffff), try fromSlice( i66, gpa, "0b11111111111111111111111111111111111111111111111111111111111111111", null, .{}, )); - try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(i66, -0x1ffffffffffffffff), try fromSlice( i66, gpa, "-0b11111111111111111111111111111111111111111111111111111111111111111", @@ -2325,7 +2334,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "32a32", &status, .{})); + 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", "{}", @@ -2337,7 +2346,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "true", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "true", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'u8'\n", "{}", .{status}); } @@ -2345,7 +2354,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "256", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "256", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", @@ -2357,7 +2366,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-129", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-129", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'i8' cannot represent value\n", "{}", @@ -2369,7 +2378,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, "-1", &status, .{})); try std.testing.expectFmt( "1:1: error: type 'u8' cannot represent value\n", "{}", @@ -2381,7 +2390,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "1.5", &status, .{})); + 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", "{}", @@ -2393,7 +2402,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "-1.0", &status, .{})); + 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", "{}", @@ -2405,7 +2414,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-0", &status, .{})); + 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 @@ -2418,7 +2427,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-0", &status, .{})); + 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 @@ -2429,15 +2438,15 @@ test "std.zon parse int" { // Negative float 0 is allowed try std.testing.expect( - std.math.isNegativeZero(try parseFromSlice(f32, gpa, "-0.0", null, .{})), + std.math.isNegativeZero(try fromSlice(f32, gpa, "-0.0", null, .{})), ); - try std.testing.expect(std.math.isPositiveZero(try parseFromSlice(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, parseFromSlice(i8, gpa, "--2", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "--2", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", @@ -2450,7 +2459,7 @@ test "std.zon parse int" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(f32, gpa, "--2.0", &status, .{}), + fromSlice(f32, gpa, "--2.0", &status, .{}), ); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", @@ -2463,7 +2472,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "0xg", &status, .{})); + 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}); } @@ -2471,7 +2480,7 @@ test "std.zon parse int" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(u8, gpa, "0123", &status, .{})); + 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 @@ -2486,7 +2495,7 @@ test "std.zon negative char" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-'a'", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-'a'", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", @@ -2496,7 +2505,7 @@ test "std.zon negative char" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i16, gpa, "-'a'", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i16, gpa, "-'a'", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", @@ -2509,44 +2518,44 @@ test "std.zon parse float" { const gpa = std.testing.allocator; // Test decimals - try std.testing.expectEqual(@as(f16, 0.5), try parseFromSlice(f16, gpa, "0.5", null, .{})); + try std.testing.expectEqual(@as(f16, 0.5), try fromSlice(f16, gpa, "0.5", null, .{})); try std.testing.expectEqual( @as(f32, 123.456), - try parseFromSlice(f32, gpa, "123.456", null, .{}), + try fromSlice(f32, gpa, "123.456", null, .{}), ); try std.testing.expectEqual( @as(f64, -123.456), - try parseFromSlice(f64, gpa, "-123.456", null, .{}), + try fromSlice(f64, gpa, "-123.456", null, .{}), ); - try std.testing.expectEqual(@as(f128, 42.5), try parseFromSlice(f128, gpa, "42.5", 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 parseFromSlice(f16, gpa, "5.0", null, .{})); - try std.testing.expectEqual(@as(f16, 5.0), try parseFromSlice(f16, gpa, "5", null, .{})); - try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102.0", null, .{})); - try std.testing.expectEqual(@as(f32, -102), try parseFromSlice(f32, gpa, "-102", null, .{})); + 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 parseFromSlice(f32, gpa, "'a'", null, .{})); - try std.testing.expectEqual(@as(f32, 'z'), try parseFromSlice(f32, gpa, "'z'", null, .{})); + 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 parseFromSlice(f32, gpa, "36893488147419103231", null, .{}), + try fromSlice(f32, gpa, "36893488147419103231", null, .{}), ); try std.testing.expectEqual( @as(f32, -36893488147419103231), - try parseFromSlice(f32, gpa, "-36893488147419103231", null, .{}), + try fromSlice(f32, gpa, "-36893488147419103231", null, .{}), ); - try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(f128, 0x1ffffffffffffffff), try fromSlice( f128, gpa, "0x1ffffffffffffffff", null, .{}, )); - try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try parseFromSlice( + try std.testing.expectEqual(@as(f32, 0x1ffffffffffffffff), try fromSlice( f32, gpa, "0x1ffffffffffffffff", @@ -2557,33 +2566,33 @@ test "std.zon parse float" { // Exponents, underscores try std.testing.expectEqual( @as(f32, 123.0E+77), - try parseFromSlice(f32, gpa, "12_3.0E+77", null, .{}), + try fromSlice(f32, gpa, "12_3.0E+77", null, .{}), ); // Hexadecimal try std.testing.expectEqual( @as(f32, 0x103.70p-5), - try parseFromSlice(f32, gpa, "0x103.70p-5", null, .{}), + try fromSlice(f32, gpa, "0x103.70p-5", null, .{}), ); try std.testing.expectEqual( @as(f32, -0x103.70), - try parseFromSlice(f32, gpa, "-0x103.70", null, .{}), + try fromSlice(f32, gpa, "-0x103.70", null, .{}), ); try std.testing.expectEqual( @as(f32, 0x1234_5678.9ABC_CDEFp-10), - try parseFromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), + try fromSlice(f32, gpa, "0x1234_5678.9ABC_CDEFp-10", null, .{}), ); // inf, nan - try std.testing.expect(std.math.isPositiveInf(try parseFromSlice(f32, gpa, "inf", null, .{}))); - try std.testing.expect(std.math.isNegativeInf(try parseFromSlice(f32, gpa, "-inf", null, .{}))); - try std.testing.expect(std.math.isNan(try parseFromSlice(f32, gpa, "nan", null, .{}))); + 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, parseFromSlice(f32, gpa, "-nan", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-nan", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", @@ -2595,7 +2604,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } @@ -2603,7 +2612,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "nan", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "nan", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } @@ -2611,7 +2620,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "inf", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "inf", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } @@ -2619,7 +2628,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(i8, gpa, "-inf", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(i8, gpa, "-inf", &status, .{})); try std.testing.expectFmt("1:1: error: expected type 'i8'\n", "{}", .{status}); } @@ -2627,7 +2636,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "foo", &status, .{})); + 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' @@ -2639,7 +2648,7 @@ test "std.zon parse float" { { var status: Status = .{}; defer status.deinit(gpa); - try std.testing.expectError(error.ParseZon, parseFromSlice(f32, gpa, "-foo", &status, .{})); + try std.testing.expectError(error.ParseZon, fromSlice(f32, gpa, "-foo", &status, .{})); try std.testing.expectFmt( "1:1: error: expected number or 'inf' after '-'\n", "{}", @@ -2653,7 +2662,7 @@ test "std.zon parse float" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - parseFromSlice(f32, gpa, "\"foo\"", &status, .{}), + fromSlice(f32, gpa, "\"foo\"", &status, .{}), ); try std.testing.expectFmt("1:1: error: expected type 'f32'\n", "{}", .{status}); } @@ -2667,7 +2676,7 @@ test "std.zon free on error" { y: []const u8, z: bool, }; - try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\ .y = "world", @@ -2683,7 +2692,7 @@ test "std.zon free on error" { []const u8, bool, }; - try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ "hello", \\ "world", @@ -2698,7 +2707,7 @@ test "std.zon free on error" { x: []const u8, y: bool, }; - try std.testing.expectError(error.ParseZon, parseFromSlice(Struct, std.testing.allocator, + try std.testing.expectError(error.ParseZon, fromSlice(Struct, std.testing.allocator, \\.{ \\ .x = "hello", \\} @@ -2707,7 +2716,7 @@ test "std.zon free on error" { // Test freeing partially allocated arrays { - try std.testing.expectError(error.ParseZon, parseFromSlice( + try std.testing.expectError(error.ParseZon, fromSlice( [3][]const u8, std.testing.allocator, \\.{ @@ -2723,7 +2732,7 @@ test "std.zon free on error" { // Test freeing partially allocated slices { - try std.testing.expectError(error.ParseZon, parseFromSlice( + try std.testing.expectError(error.ParseZon, fromSlice( [][]const u8, std.testing.allocator, \\.{ @@ -2741,20 +2750,20 @@ test "std.zon free on error" { // unions. try std.testing.expectEqual( @as(f32, 1.5), - (try parseFromSlice(union { x: f32 }, std.testing.allocator, ".{ .x = 1.5 }", null, .{})).x, + (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 parseFromSlice( + const result = try fromSlice( union { x: []const u8 }, std.testing.allocator, ".{ .x = \"foo\" }", null, .{}, ); - defer parseFree(std.testing.allocator, result.x); + defer free(std.testing.allocator, result.x); try std.testing.expectEqualStrings("foo", result.x); } @@ -2766,14 +2775,14 @@ test "std.zon free on error" { union { x: []const u8 }, bool, }; - const result = try parseFromSlice( + const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, true }", null, .{ .free_on_error = false }, ); - defer parseFree(std.testing.allocator, result[0].x); + defer free(std.testing.allocator, result[0].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expect(result[1]); } @@ -2784,7 +2793,7 @@ test "std.zon free on error" { a: union { x: []const u8 }, b: bool, }; - const result = try parseFromSlice( + const result = try fromSlice( S, std.testing.allocator, ".{ .a = .{ .x = \"foo\" }, .b = true }", @@ -2793,7 +2802,7 @@ test "std.zon free on error" { .free_on_error = false, }, ); - defer parseFree(std.testing.allocator, result.a.x); + defer free(std.testing.allocator, result.a.x); try std.testing.expectEqualStrings("foo", result.a.x); try std.testing.expect(result.b); } @@ -2801,7 +2810,7 @@ test "std.zon free on error" { // Again but for arrays. { const S = [2]union { x: []const u8 }; - const result = try parseFromSlice( + const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", @@ -2810,8 +2819,8 @@ test "std.zon free on error" { .free_on_error = false, }, ); - defer parseFree(std.testing.allocator, result[0].x); - defer parseFree(std.testing.allocator, result[1].x); + 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); } @@ -2819,7 +2828,7 @@ test "std.zon free on error" { // Again but for slices. { const S = []union { x: []const u8 }; - const result = try parseFromSlice( + const result = try fromSlice( S, std.testing.allocator, ".{ .{ .x = \"foo\" }, .{ .x = \"bar\" } }", @@ -2829,8 +2838,8 @@ test "std.zon free on error" { }, ); defer std.testing.allocator.free(result); - defer parseFree(std.testing.allocator, result[0].x); - defer parseFree(std.testing.allocator, result[1].x); + defer free(std.testing.allocator, result[0].x); + defer free(std.testing.allocator, result[1].x); try std.testing.expectEqualStrings("foo", result[0].x); try std.testing.expectEqualStrings("bar", result[1].x); } diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 2f6c7e7b761e..7d1656247c92 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -1,24 +1,29 @@ -const std = @import("std"); +//! ZON can be serialized with `serialize`. +//! +//! The following functions are provided for serializing recursive types: +//! * `serializeMaxDepth` +//! * `serializeArbitraryDepth` +//! +//! For additional control over serialization, see `Serializer`. +//! +//! Transitively Supported types: +//! * bools +//! * fixed sized numeric types +//! * exhaustive enums (non-exhaustive enums may have no literal representation) +//! * enum literals +//! * slices +//! * arrays +//! * structs +//! * tagged unions +//! * optionals +//! * null +//! +//! Unsupported types will fail to serialize at compile time. -/// Configuration for stringification. -/// -/// See `StringifyOptions` for more details. -pub const StringifierOptions = struct { - /// If false, only syntactically necessary whitespace is emitted. - whitespace: bool = true, -}; - -/// Options for stringification of an individual value. -/// -/// See `StringifyOptions` for more details. -pub const StringifyValueOptions = struct { - emit_utf8_codepoints: bool = false, - emit_strings_as_containers: bool = false, - emit_default_optional_fields: bool = true, -}; +const std = @import("std"); -/// All stringify options. -pub const StringifyOptions = struct { +/// Options for `serialize`. +pub const SerializeOptions = struct { /// If false, all whitespace is emitted. Otherwise, whitespace is emitted in the standard Zig /// style when possible. whitespace: bool = true, @@ -33,47 +38,15 @@ pub const StringifyOptions = struct { emit_default_optional_fields: bool = true, }; -/// Options for manual serializaation of container types. -pub const StringifyContainerOptions = struct { - /// The whitespace style that should be used for this container. Ignored if whitespace is off. - whitespace_style: union(enum) { - /// If true, wrap every field/item. If false do not. - wrap: bool, - /// Automatically decide whether to wrap or not based on the number of fields. Following - /// the standard rule of thumb, containers with more than two fields are wrapped. - fields: usize, - } = .{ .wrap = true }, - - fn shouldWrap(self: StringifyContainerOptions) bool { - return switch (self.whitespace_style) { - .wrap => |wrap| wrap, - .fields => |fields| fields > 2, - }; - } -}; - -/// Serialize the given value to ZON. +/// Serialize the given value as ZON. /// /// It is asserted at comptime that `@TypeOf(val)` is not a recursive type. -pub fn stringify( - /// The value to serialize. May only transitively contain the following supported types: - /// * bools - /// * fixed sized numeric types - /// * exhaustive enums, enum literals - /// * Non-exhaustive enums may hold values that have no literal representation, and - /// therefore cannot be stringified in a way that allows round trips back through the - /// parser. There are plans to resolve this in the future. - /// * slices - /// * arrays - /// * structures - /// * tagged unions - /// * optionals - /// * null +pub fn serialize( val: anytype, - comptime options: StringifyOptions, + comptime options: SerializeOptions, writer: anytype, ) @TypeOf(writer).Error!void { - var serializer = stringifier(writer, .{ + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ .whitespace = options.whitespace, }); try serializer.value(val, .{ @@ -83,11 +56,16 @@ pub fn stringify( }); } -/// Like `stringify`, but recursive types are allowed. +/// Like `serialize`, but recursive types are allowed. /// -/// Returns `error.MaxDepth` if `depth` is exceeded. -pub fn stringifyMaxDepth(val: anytype, comptime options: StringifyOptions, writer: anytype, depth: usize) Stringifier(@TypeOf(writer)).MaxDepthError!void { - var serializer = stringifier(writer, .{ +/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. +pub fn serializeMaxDepth( + val: anytype, + comptime options: SerializeOptions, + writer: anytype, + depth: usize, +) Serializer(@TypeOf(writer)).ExceededMaxDepth!void { + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ .whitespace = options.whitespace, }); try serializer.valueMaxDepth(val, .{ @@ -97,11 +75,15 @@ pub fn stringifyMaxDepth(val: anytype, comptime options: StringifyOptions, write }, depth); } -/// Like `stringify`, but recursive types are allowed. +/// Like `serialize`, but recursive types are allowed. /// /// It is the caller's responsibility to ensure that `val` does not contain cycles. -pub fn stringifyArbitraryDepth(val: anytype, comptime options: StringifyOptions, writer: anytype) @TypeOf(writer).Error!void { - var serializer = stringifier(writer, .{ +pub fn serializeArbitraryDepth( + val: anytype, + comptime options: SerializeOptions, + writer: anytype, +) @TypeOf(writer).Error!void { + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ .whitespace = options.whitespace, }); try serializer.valueArbitraryDepth(val, .{ @@ -244,9 +226,41 @@ test "std.zon checkValueDepth" { try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); } -/// Lower level control over stringification, you can create a new instance with `stringifier`. +/// Options for `Serializer`. +pub const SerializerOptions = struct { + /// If false, only syntactically necessary whitespace is emitted. + whitespace: bool = true, +}; + +/// Options for serialization of an individual value. +pub const ValueOptions = struct { + emit_utf8_codepoints: bool = false, + emit_strings_as_containers: bool = false, + emit_default_optional_fields: bool = true, +}; + +/// Options for manual serialization of container types. +pub const SerializeContainerOptions = struct { + /// The whitespace style that should be used for this container. Ignored if whitespace is off. + whitespace_style: union(enum) { + /// If true, wrap every field/item. If false do not. + wrap: bool, + /// Automatically decide whether to wrap or not based on the number of fields. Following + /// the standard rule of thumb, containers with more than two fields are wrapped. + fields: usize, + } = .{ .wrap = true }, + + fn shouldWrap(self: SerializeContainerOptions) bool { + return switch (self.whitespace_style) { + .wrap => |wrap| wrap, + .fields => |fields| fields > 2, + }; + } +}; + +/// Lower level control over serialization, you can create a new instance with `serializer`. /// -/// Useful when you want control over which fields/items are stringified, how they're represented, +/// Useful when you want control over which fields/items are serialized, how they're represented, /// or want to write a ZON object that does not exist in memory. /// /// You can serialize values with `value`. To serialize recursive types, the following are provided: @@ -270,25 +284,25 @@ test "std.zon checkValueDepth" { /// /// # Example /// ```zig -/// var serializer = stringifier(writer, .{}); +/// var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); /// var vec2 = try serializer.startStruct(.{}); /// try vec2.field("x", 1.5, .{}); /// try vec2.fieldPrefix(); /// try serializer.value(2.5); /// try vec2.finish(); /// ``` -pub fn Stringifier(comptime Writer: type) type { +pub fn Serializer(comptime Writer: type) type { return struct { const Self = @This(); - pub const MaxDepthError = error{MaxDepth} || Writer.Error; + pub const ExceededMaxDepth = error{MaxDepth} || Writer.Error; - options: StringifierOptions, + options: SerializerOptions, indent_level: u8, writer: Writer, - /// Initialize a stringifier. - fn init(writer: Writer, options: StringifierOptions) Self { + /// Initialize a serializer. + fn init(writer: Writer, options: SerializerOptions) Self { return .{ .options = options, .writer = writer, @@ -296,20 +310,29 @@ pub fn Stringifier(comptime Writer: type) type { }; } - /// Serialize a value, similar to `stringify`. - pub fn value(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + /// Serialize a value, similar to `serialize`. + pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { comptimeAssertNoRecursion(@TypeOf(val)); return self.valueArbitraryDepth(val, options); } - /// Serialize a value, similar to `stringifyMaxDepth`. - pub fn valueMaxDepth(self: *Self, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + /// Serialize a value, similar to `serializeMaxDepth`. + pub fn valueMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { try checkValueDepth(val, depth); return self.valueArbitraryDepth(val, options); } - /// Serialize a value, similar to `stringifyArbitraryDepth`. - pub fn valueArbitraryDepth(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + /// Serialize a value, similar to `serializeArbitraryDepth`. + pub fn valueArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { switch (@typeInfo(@TypeOf(val))) { .int => |int_info| if (options.emit_utf8_codepoints and int_info.signedness == .unsigned and @@ -344,13 +367,17 @@ pub fn Stringifier(comptime Writer: type) type { try self.writer.writeByte('.'); try self.ident(@tagName(val)); } else { - @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums"); + @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", + ); }, .void => try self.writer.writeAll("{}"), .pointer => |pointer| { const child_type = switch (@typeInfo(pointer.child)) { .array => |array| array.child, - else => if (pointer.size != .Slice) @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type") else pointer.child, + else => if (pointer.size != .Slice) @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", + ) else pointer.child, }; if (child_type == u8 and !options.emit_strings_as_containers) { try self.string(val); @@ -359,14 +386,18 @@ pub fn Stringifier(comptime Writer: type) type { } }, .array => { - var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + 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 } }); + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, + ); inline for (val) |field_value| { try container.fieldArbitraryDepth(field_value, options); } @@ -381,7 +412,9 @@ pub fn Stringifier(comptime Writer: type) type { inline for (@"struct".fields, &skipped) |field_info, *skip| { if (field_info.default_value) |default_field_value_opaque| { const field_value = @field(val, field_info.name); - const default_field_value: *const @TypeOf(field_value) = @ptrCast(@alignCast(default_field_value_opaque)); + const default_field_value: *const @TypeOf(field_value) = @ptrCast( + @alignCast(default_field_value_opaque), + ); if (std.meta.eql(field_value, default_field_value.*)) { skip.* = true; fields -= 1; @@ -392,10 +425,16 @@ pub fn Stringifier(comptime Writer: type) type { }; // Emit those fields - var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 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.fieldArbitraryDepth( + field_info.name, + @field(val, field_info.name), + options, + ); } } try container.finish(); @@ -405,7 +444,11 @@ pub fn Stringifier(comptime Writer: type) type { } else { var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); switch (val) { - inline else => |pl, tag| try container.fieldArbitraryDepth(@tagName(tag), pl, options), + inline else => |pl, tag| try container.fieldArbitraryDepth( + @tagName(tag), + pl, + options, + ), } try container.finish(); }, @@ -478,15 +521,20 @@ pub fn Stringifier(comptime Writer: type) type { /// Like `value`, but always serializes `val` as a slice. /// /// Will fail at comptime if `val` is not an array or slice. - pub fn slice(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + pub fn slice(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { comptimeAssertNoRecursion(@TypeOf(val)); try self.sliceArbitraryDepth(val, options); } /// Like `value`, but recursive types are allowed. /// - /// Returns `error.MaxDepthError` if `depth` is exceeded. - pub fn sliceMaxDepth(self: *Self, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. + pub fn sliceMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { try checkValueDepth(val, depth); try self.sliceArbitraryDepth(val, options); } @@ -494,11 +542,15 @@ pub fn Stringifier(comptime Writer: type) type { /// Like `value`, but recursive types are allowed. /// /// It is the caller's responsibility to ensure that `val` does not contain cycles. - pub fn sliceArbitraryDepth(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + pub fn sliceArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { try self.sliceImpl(val, options); } - fn sliceImpl(self: *Self, val: anytype, options: StringifyValueOptions) Writer.Error!void { + fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); for (val) |item_val| { try container.itemArbitraryDepth(item_val, options); @@ -523,7 +575,11 @@ pub fn Stringifier(comptime Writer: type) type { /// /// 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 { + 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) { @@ -561,17 +617,17 @@ pub fn Stringifier(comptime Writer: type) type { } /// Create a `Struct` for writing ZON structs field by field. - pub fn startStruct(self: *Self, options: StringifyContainerOptions) Writer.Error!Struct { + 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: StringifyContainerOptions) Writer.Error!Tuple { + pub fn startTuple(self: *Self, options: SerializeContainerOptions) Writer.Error!Tuple { return Tuple.start(self, options); } /// Creates a `Slice` for writing ZON slices item by item. - pub fn startSlice(self: *Self, options: StringifyContainerOptions) Writer.Error!Slice { + pub fn startSlice(self: *Self, options: SerializeContainerOptions) Writer.Error!Slice { return Slice.start(self, options); } @@ -605,7 +661,7 @@ pub fn Stringifier(comptime Writer: type) type { pub const Tuple = struct { container: Container, - fn start(parent: *Self, options: StringifyContainerOptions) Writer.Error!Tuple { + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple { return .{ .container = try Container.start(parent, .anon, options), }; @@ -620,17 +676,31 @@ pub fn Stringifier(comptime Writer: type) type { } /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. - pub fn field(self: *Tuple, val: anytype, options: StringifyValueOptions) Writer.Error!void { + 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: StringifyValueOptions, depth: usize) MaxDepthError!void { + pub fn fieldMaxDepth( + self: *Tuple, + val: anytype, + options: ValueOptions, + depth: usize, + ) 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: StringifyValueOptions) Writer.Error!void { + /// 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); } @@ -645,7 +715,7 @@ pub fn Stringifier(comptime Writer: type) type { pub const Struct = struct { container: Container, - fn start(parent: *Self, options: StringifyContainerOptions) Writer.Error!Struct { + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct { return .{ .container = try Container.start(parent, .named, options), }; @@ -660,17 +730,34 @@ pub fn Stringifier(comptime Writer: type) type { } /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. - pub fn field(self: *Struct, name: []const u8, val: anytype, options: StringifyValueOptions) Writer.Error!void { + 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: StringifyValueOptions, depth: usize) MaxDepthError!void { + pub fn fieldMaxDepth( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) 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: StringifyValueOptions) Writer.Error!void { + /// 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); } @@ -686,7 +773,7 @@ pub fn Stringifier(comptime Writer: type) type { pub const Slice = struct { container: Container, - fn start(parent: *Self, options: StringifyContainerOptions) Writer.Error!Slice { + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Slice { try parent.writer.writeByte('&'); return .{ .container = try Container.start(parent, .anon, options), @@ -702,17 +789,31 @@ pub fn Stringifier(comptime Writer: type) type { } /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. - pub fn item(self: *Slice, val: anytype, options: StringifyValueOptions) Writer.Error!void { + pub fn item( + self: *Slice, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { try self.container.field(null, val, options); } /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. - pub fn itemMaxDepth(self: *Slice, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + pub fn itemMaxDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { try self.container.fieldMaxDepth(null, val, options, depth); } - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueArbitraryDepth`. - pub fn itemArbitraryDepth(self: *Slice, val: anytype, options: StringifyValueOptions) Writer.Error!void { + /// Serialize an item. Equivalent to calling `itemPrefix` followed by + /// `valueArbitraryDepth`. + pub fn itemArbitraryDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { try self.container.fieldArbitraryDepth(null, val, options); } @@ -728,10 +829,14 @@ pub fn Stringifier(comptime Writer: type) type { serializer: *Self, field_style: FieldStyle, - options: StringifyContainerOptions, + options: SerializeContainerOptions, empty: bool, - fn start(serializer: *Self, field_style: FieldStyle, options: StringifyContainerOptions) Writer.Error!Container { + fn start( + serializer: *Self, + field_style: FieldStyle, + options: SerializeContainerOptions, + ) Writer.Error!Container { if (options.shouldWrap()) serializer.indent_level +|= 1; try serializer.writer.writeAll(".{"); return .{ @@ -779,17 +884,33 @@ pub fn Stringifier(comptime Writer: type) type { } } - fn field(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions) Writer.Error!void { + fn field( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { comptimeAssertNoRecursion(@TypeOf(val)); try self.fieldArbitraryDepth(name, val, options); } - fn fieldMaxDepth(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions, depth: usize) MaxDepthError!void { + fn fieldMaxDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { try checkValueDepth(val, depth); try self.fieldArbitraryDepth(name, val, options); } - fn fieldArbitraryDepth(self: *Container, name: ?[]const u8, val: anytype, options: StringifyValueOptions) Writer.Error!void { + fn fieldArbitraryDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { try self.fieldPrefix(name); try self.serializer.valueArbitraryDepth(val, options); } @@ -810,89 +931,96 @@ pub fn Stringifier(comptime Writer: type) type { }; } -/// Creates an instance of `Stringifier`. -pub fn stringifier(writer: anytype, options: StringifierOptions) Stringifier(@TypeOf(writer)) { - return Stringifier(@TypeOf(writer)).init(writer, options); -} - -fn expectStringifyEqual(expected: []const u8, value: anytype, comptime options: StringifyOptions) !void { +fn expectSerializeEqual( + expected: []const u8, + value: anytype, + comptime options: SerializeOptions, +) !void { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - try stringify(value, options, buf.writer()); + try serialize(value, options, buf.writer()); try std.testing.expectEqualStrings(expected, buf.items); } test "std.zon stringify whitespace, high level API" { - try expectStringifyEqual(".{}", .{}, .{}); - try expectStringifyEqual(".{}", .{}, .{ .whitespace = false }); + try expectSerializeEqual(".{}", .{}, .{}); + try expectSerializeEqual(".{}", .{}, .{ .whitespace = false }); - try expectStringifyEqual(".{1}", .{1}, .{}); - try expectStringifyEqual(".{1}", .{1}, .{ .whitespace = false }); + try expectSerializeEqual(".{1}", .{1}, .{}); + try expectSerializeEqual(".{1}", .{1}, .{ .whitespace = false }); - try expectStringifyEqual(".{1}", @as([1]u32, .{1}), .{}); - try expectStringifyEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false }); + try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{}); + try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false }); - try expectStringifyEqual("&.{1}", @as([]const u32, &.{1}), .{}); - try expectStringifyEqual("&.{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); + try expectSerializeEqual("&.{1}", @as([]const u32, &.{1}), .{}); + try expectSerializeEqual("&.{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); - try expectStringifyEqual(".{ .x = 1 }", .{ .x = 1 }, .{}); - try expectStringifyEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false }); + try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{}); + try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false }); - try expectStringifyEqual(".{ 1, 2 }", .{ 1, 2 }, .{}); - try expectStringifyEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false }); + try expectSerializeEqual(".{ 1, 2 }", .{ 1, 2 }, .{}); + try expectSerializeEqual(".{1,2}", .{ 1, 2 }, .{ .whitespace = false }); - try expectStringifyEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{}); - try expectStringifyEqual(".{1,2}", @as([2]u32, .{ 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 expectStringifyEqual("&.{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{}); - try expectStringifyEqual("&.{1,2}", @as([]const 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 expectStringifyEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{}); - try expectStringifyEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 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 expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ 1, \\ 2, \\ 3, \\} , .{ 1, 2, 3 }, .{}); - try expectStringifyEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false }); + try expectSerializeEqual(".{1,2,3}", .{ 1, 2, 3 }, .{ .whitespace = false }); - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ 1, \\ 2, \\ 3, \\} , @as([3]u32, .{ 1, 2, 3 }), .{}); - try expectStringifyEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false }); + try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false }); - try expectStringifyEqual( + try expectSerializeEqual( \\&.{ \\ 1, \\ 2, \\ 3, \\} , @as([]const u32, &.{ 1, 2, 3 }), .{}); - try expectStringifyEqual("&.{1,2,3}", @as([]const u32, &.{ 1, 2, 3 }), .{ .whitespace = false }); + try expectSerializeEqual( + "&.{1,2,3}", + @as([]const u32, &.{ 1, 2, 3 }), + .{ .whitespace = false }, + ); - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ .x = 1, \\ .y = 2, \\ .z = 3, \\} , .{ .x = 1, .y = 2, .z = 3 }, .{}); - try expectStringifyEqual(".{.x=1,.y=2,.z=3}", .{ .x = 1, .y = 2, .z = 3 }, .{ .whitespace = false }); + 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 expectStringifyEqual(".{ .b = 1 }", Union{ .b = 1 }, .{}); - try expectStringifyEqual(".{.b=1}", Union{ .b = 1 }, .{ .whitespace = false }); + 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 expectStringifyEqual( + try expectSerializeEqual( \\.{ .inner = .{ \\ 1, \\ 2, @@ -905,7 +1033,7 @@ test "std.zon stringify whitespace, low level API" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const writer = buffer.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); inline for (.{ true, false }) |whitespace| { serializer.options = .{ .whitespace = whitespace }; @@ -1249,7 +1377,10 @@ test "std.zon stringify whitespace, low level API" { \\} } , buffer.items); } else { - try std.testing.expectEqualStrings(".{.first=.{1,2,3},.second=.{4,5,6}}", buffer.items); + try std.testing.expectEqualStrings( + ".{.first=.{1,2,3},.second=.{4,5,6}}", + buffer.items, + ); } buffer.clearRetainingCapacity(); } @@ -1260,7 +1391,7 @@ test "std.zon stringify utf8 codepoints" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const writer = buffer.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); // Minimal case try serializer.utf8Codepoint('a'); @@ -1351,7 +1482,7 @@ test "std.zon stringify strings" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const writer = buffer.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); // Minimal case try serializer.string("abcâš¡\n"); @@ -1405,8 +1536,8 @@ test "std.zon stringify strings" { , buffer.items); buffer.clearRetainingCapacity(); - // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can round trip - // correctly. + // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can + // round trip correctly. try serializer.value("abc".*, .{}); try std.testing.expectEqualStrings( \\.{ @@ -1422,7 +1553,7 @@ test "std.zon stringify multiline strings" { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); const writer = buf.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); inline for (.{ true, false }) |whitespace| { serializer.options.whitespace = whitespace; @@ -1481,9 +1612,18 @@ test "std.zon stringify multiline strings" { } { - try std.testing.expectError(error.InnerCarriageReturn, serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{})); - try std.testing.expectError(error.InnerCarriageReturn, serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{})); - try std.testing.expectError(error.InnerCarriageReturn, serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{})); + try std.testing.expectError( + error.InnerCarriageReturn, + serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), + ); + try std.testing.expectError( + error.InnerCarriageReturn, + serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), + ); + try std.testing.expectError( + error.InnerCarriageReturn, + serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); } @@ -1513,7 +1653,7 @@ test "std.zon stringify skip default fields" { }; // Not skipping if not set - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ .x = 2, \\ .y = 3, @@ -1553,7 +1693,7 @@ test "std.zon stringify skip default fields" { ); // Top level defaults - try expectStringifyEqual( + try expectSerializeEqual( \\.{ .y = 3, .inner3 = .{ \\ 'a', \\ 'b', @@ -1580,8 +1720,9 @@ test "std.zon stringify skip default fields" { }, ); - // Inner types having defaults, and defaults changing the number of fields affecting the formatting - try expectStringifyEqual( + // Inner types having defaults, and defaults changing the number of fields affecting the + // formatting + try expectSerializeEqual( \\.{ \\ .y = 3, \\ .inner1 = .{ .b = '2', .c = '3' }, @@ -1615,13 +1756,13 @@ test "std.zon stringify skip default fields" { const DefaultStrings = struct { foo: []const u8 = "abc", }; - try expectStringifyEqual( + try expectSerializeEqual( \\.{} , DefaultStrings{ .foo = "abc" }, .{ .emit_default_optional_fields = false }, ); - try expectStringifyEqual( + try expectSerializeEqual( \\.{ .foo = "abcd" } , DefaultStrings{ .foo = "abcd" }, @@ -1636,23 +1777,26 @@ test "std.zon depth limits" { const Recurse = struct { r: []const @This() }; // Normal operation - try stringifyMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); + try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); buf.clearRetainingCapacity(); - try stringifyArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer()); + 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.MaxDepth, stringifyMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3)); + try std.testing.expectError( + error.MaxDepth, + 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 stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 2); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1660,7 +1804,7 @@ test "std.zon depth limits" { // Unchecked passing on recursive type { const maybe_recurse = Recurse{ .r = &.{} }; - try stringifyArbitraryDepth(maybe_recurse, .{}, buf.writer()); + try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1669,7 +1813,10 @@ test "std.zon depth limits" { { var maybe_recurse = Recurse{ .r = &.{} }; maybe_recurse.r = &.{.{ .r = &.{} }}; - try std.testing.expectError(error.MaxDepth, stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 2)); + try std.testing.expectError( + error.MaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); } @@ -1679,13 +1826,20 @@ test "std.zon depth limits" { var temp: [1]Recurse = .{.{ .r = &.{} }}; const maybe_recurse: []const Recurse = &temp; - try std.testing.expectError(error.MaxDepth, stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 2)); + try std.testing.expectError( + error.MaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - var serializer = stringifier(buf.writer(), .{}); + const writer = buf.writer(); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); - try std.testing.expectError(error.MaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 2)); + try std.testing.expectError( + error.MaxDepth, + serializer.sliceMaxDepth(maybe_recurse, .{}, 2), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1699,11 +1853,12 @@ test "std.zon depth limits" { var temp: [1]Recurse = .{.{ .r = &.{} }}; const maybe_recurse: []const Recurse = &temp; - try stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 3); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); - var serializer = stringifier(buf.writer(), .{}); + const writer = buf.writer(); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); try serializer.sliceMaxDepth(maybe_recurse, .{}, 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); @@ -1720,12 +1875,19 @@ test "std.zon depth limits" { temp[0].r = &temp; const maybe_recurse: []const Recurse = &temp; - try std.testing.expectError(error.MaxDepth, stringifyMaxDepth(maybe_recurse, .{}, buf.writer(), 128)); + try std.testing.expectError( + error.MaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - var serializer = stringifier(buf.writer(), .{}); - try std.testing.expectError(error.MaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 128)); + const writer = buf.writer(); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + try std.testing.expectError( + error.MaxDepth, + serializer.sliceMaxDepth(maybe_recurse, .{}, 128), + ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); } @@ -1733,7 +1895,7 @@ test "std.zon depth limits" { // Max depth on other parts of the lower level API { const writer = buf.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); const maybe_recurse: []const Recurse = &.{}; @@ -1785,7 +1947,7 @@ test "std.zon stringify primitives" { // Issue: https://github.com/ziglang/zig/issues/20880 if (@import("builtin").zig_backend == .stage2_c) return error.SkipZigTest; - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ .a = 1.5, \\ .b = 0.3333333333333333333333333333333333, @@ -1810,7 +1972,7 @@ test "std.zon stringify primitives" { .{}, ); - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ .a = 18446744073709551616, \\ .b = -18446744073709551616, @@ -1829,7 +1991,7 @@ test "std.zon stringify primitives" { .{}, ); - try expectStringifyEqual( + try expectSerializeEqual( \\.{ \\ .a = true, \\ .b = false, @@ -1849,7 +2011,7 @@ test "std.zon stringify primitives" { ); const Struct = struct { x: f32, y: f32 }; - try expectStringifyEqual( + try expectSerializeEqual( ".{ .a = .{ .x = 1, .y = 2 }, .b = null }", .{ .a = @as(?Struct, .{ .x = 1, .y = 2 }), @@ -1862,7 +2024,7 @@ test "std.zon stringify primitives" { foo, bar, }; - try expectStringifyEqual( + try expectSerializeEqual( ".{ .a = .foo, .b = .foo }", .{ .a = .foo, @@ -1876,7 +2038,7 @@ test "std.zon stringify ident" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const writer = buffer.writer(); - var serializer = stringifier(writer, .{}); + var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); try serializer.ident("a"); try std.testing.expectEqualStrings("a", buffer.items); @@ -1913,7 +2075,7 @@ test "std.zon stringify ident" { const Enum = enum { @"foo bar", }; - try expectStringifyEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{ + try expectSerializeEqual(".{ .@\"var\" = .@\"foo bar\", .@\"1\" = .@\"foo bar\" }", .{ .@"var" = .@"foo bar", .@"1" = Enum.@"foo bar", }, .{}); From c837afb3556d7ad1eafe910e9d1cd6825a0212fd Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 23:02:06 -0800 Subject: [PATCH 48/51] Replaces serialzier writer with AnyWriter, no longer needs to be generic --- lib/std/zon/stringify.zig | 1229 +++++++++++++++++++------------------ 1 file changed, 615 insertions(+), 614 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 7d1656247c92..1b7341d0b2d8 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -44,9 +44,9 @@ pub const SerializeOptions = struct { pub fn serialize( val: anytype, comptime options: SerializeOptions, - writer: anytype, + writer: std.io.AnyWriter, ) @TypeOf(writer).Error!void { - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ + var serializer = Serializer.init(writer, .{ .whitespace = options.whitespace, }); try serializer.value(val, .{ @@ -62,10 +62,10 @@ pub fn serialize( pub fn serializeMaxDepth( val: anytype, comptime options: SerializeOptions, - writer: anytype, + writer: std.io.AnyWriter, depth: usize, -) Serializer(@TypeOf(writer)).ExceededMaxDepth!void { - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ +) Serializer.ExceededMaxDepth!void { + var serializer = Serializer.init(writer, .{ .whitespace = options.whitespace, }); try serializer.valueMaxDepth(val, .{ @@ -81,9 +81,9 @@ pub fn serializeMaxDepth( pub fn serializeArbitraryDepth( val: anytype, comptime options: SerializeOptions, - writer: anytype, + writer: std.io.AnyWriter, ) @TypeOf(writer).Error!void { - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{ + var serializer = Serializer.init(writer, .{ .whitespace = options.whitespace, }); try serializer.valueArbitraryDepth(val, .{ @@ -160,8 +160,8 @@ test "std.zon typeIsRecursive" { })); } -fn checkValueDepth(val: anytype, depth: usize) error{MaxDepth}!void { - if (depth == 0) return error.MaxDepth; +fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { + if (depth == 0) return error.ExceededMaxDepth; const child_depth = depth - 1; switch (@typeInfo(@TypeOf(val))) { @@ -192,7 +192,7 @@ fn checkValueDepth(val: anytype, depth: usize) error{MaxDepth}!void { fn expectValueDepthEquals(expected: usize, value: anytype) !void { try checkValueDepth(value, expected); - try std.testing.expectError(error.MaxDepth, checkValueDepth(value, expected - 1)); + try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(value, expected - 1)); } test "std.zon checkValueDepth" { @@ -284,652 +284,662 @@ pub const SerializeContainerOptions = struct { /// /// # Example /// ```zig -/// var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); +/// var serializer = Serializer.init(writer, .{}); /// var vec2 = try serializer.startStruct(.{}); /// try vec2.field("x", 1.5, .{}); /// try vec2.fieldPrefix(); /// try serializer.value(2.5); /// try vec2.finish(); /// ``` -pub fn Serializer(comptime Writer: type) type { - return struct { - const Self = @This(); +pub const Serializer = struct { + const Self = @This(); - pub const ExceededMaxDepth = error{MaxDepth} || Writer.Error; + pub const ExceededMaxDepth = error{ExceededMaxDepth} || std.io.AnyWriter.Error; - options: SerializerOptions, - indent_level: u8, - writer: Writer, + options: SerializerOptions, + indent_level: u8, + writer: std.io.AnyWriter, - /// Initialize a serializer. - fn init(writer: Writer, options: SerializerOptions) Self { - return .{ - .options = options, - .writer = writer, - .indent_level = 0, - }; - } + /// Initialize a serializer. + fn init(writer: std.io.AnyWriter, options: SerializerOptions) Self { + return .{ + .options = options, + .writer = writer, + .indent_level = 0, + }; + } - /// Serialize a value, similar to `serialize`. - pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - return self.valueArbitraryDepth(val, options); - } + /// Serialize a value, similar to `serialize`. + pub fn value(self: *Self, val: anytype, options: ValueOptions) std.io.AnyWriter.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + return self.valueArbitraryDepth(val, options); + } - /// Serialize a value, similar to `serializeMaxDepth`. - pub fn valueMaxDepth( - self: *Self, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try checkValueDepth(val, depth); - return self.valueArbitraryDepth(val, options); - } + /// Serialize a value, similar to `serializeMaxDepth`. + pub fn valueMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { + try checkValueDepth(val, depth); + return self.valueArbitraryDepth(val, options); + } - /// Serialize a value, similar to `serializeArbitraryDepth`. - pub fn valueArbitraryDepth( - self: *Self, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - switch (@typeInfo(@TypeOf(val))) { - .int => |int_info| if (options.emit_utf8_codepoints and - int_info.signedness == .unsigned and - int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) - { - self.utf8Codepoint(val) catch |err| switch (err) { - error.InvalidCodepoint => unreachable, // Already validated - else => |e| return e, - }; - } else { - try self.int(val); - }, - .comptime_int => if (options.emit_utf8_codepoints and - val > 0 and - val <= std.math.maxInt(u21) and - std.unicode.utf8ValidCodepoint(val)) - { - self.utf8Codepoint(val) catch |err| switch (err) { - error.InvalidCodepoint => unreachable, // Already validated - else => |e| return e, - }; - } else { - try self.int(val); - }, - .float, .comptime_float => try self.float(val), - .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), - .enum_literal => { - try self.writer.writeByte('.'); - try self.ident(@tagName(val)); - }, - .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { - try self.writer.writeByte('.'); - try self.ident(@tagName(val)); - } else { - @compileError( - @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", - ); - }, - .void => try self.writer.writeAll("{}"), - .pointer => |pointer| { - const child_type = switch (@typeInfo(pointer.child)) { - .array => |array| array.child, - else => if (pointer.size != .Slice) @compileError( - @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", - ) else pointer.child, - }; - if (child_type == u8 and !options.emit_strings_as_containers) { - try self.string(val); - } else { - try self.sliceImpl(val, options); - } - }, - .array => { - var container = try self.startTuple( - .{ .whitespace_style = .{ .fields = val.len } }, - ); - for (val) |item_val| { - try container.fieldArbitraryDepth(item_val, options); - } - try container.finish(); - }, - .@"struct" => |@"struct"| if (@"struct".is_tuple) { - var container = try self.startTuple( - .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, - ); - inline for (val) |field_value| { - try container.fieldArbitraryDepth(field_value, options); - } - try container.finish(); + /// Serialize a value, similar to `serializeArbitraryDepth`. + pub fn valueArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .int => |int_info| if (options.emit_utf8_codepoints and + int_info.signedness == .unsigned and + int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .comptime_int => if (options.emit_utf8_codepoints and + val > 0 and + val <= std.math.maxInt(u21) and + std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .float, .comptime_float => try self.float(val), + .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), + .enum_literal => { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + }, + .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + } else { + @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", + ); + }, + .void => try self.writer.writeAll("{}"), + .pointer => |pointer| { + const child_type = switch (@typeInfo(pointer.child)) { + .array => |array| array.child, + else => if (pointer.size != .Slice) @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", + ) else pointer.child, + }; + if (child_type == u8 and !options.emit_strings_as_containers) { + try self.string(val); } else { - // Decide which fields to emit - const fields, const skipped = if (options.emit_default_optional_fields) b: { - break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; - } else b: { - var fields = @"struct".fields.len; - var skipped = [1]bool{false} ** @"struct".fields.len; - inline for (@"struct".fields, &skipped) |field_info, *skip| { - if (field_info.default_value) |default_field_value_opaque| { - const field_value = @field(val, field_info.name); - const default_field_value: *const @TypeOf(field_value) = @ptrCast( - @alignCast(default_field_value_opaque), - ); - if (std.meta.eql(field_value, default_field_value.*)) { - skip.* = true; - fields -= 1; - } - } - } - break :b .{ fields, skipped }; - }; - - // Emit those fields - var container = try self.startStruct( - .{ .whitespace_style = .{ .fields = fields } }, - ); - inline for (@"struct".fields, skipped) |field_info, skip| { - if (!skip) { - try container.fieldArbitraryDepth( - field_info.name, - @field(val, field_info.name), - options, + try self.sliceImpl(val, options); + } + }, + .array => { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = val.len } }, + ); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, + ); + inline for (val) |field_value| { + try container.fieldArbitraryDepth(field_value, options); + } + try container.finish(); + } else { + // Decide which fields to emit + const fields, const skipped = if (options.emit_default_optional_fields) b: { + break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; + } else b: { + var fields = @"struct".fields.len; + var skipped = [1]bool{false} ** @"struct".fields.len; + inline for (@"struct".fields, &skipped) |field_info, *skip| { + if (field_info.default_value) |default_field_value_opaque| { + const field_value = @field(val, field_info.name); + const default_field_value: *const @TypeOf(field_value) = @ptrCast( + @alignCast(default_field_value_opaque), ); + if (std.meta.eql(field_value, default_field_value.*)) { + skip.* = true; + fields -= 1; + } } } - try container.finish(); - }, - .@"union" => |@"union"| if (@"union".tag_type == null) { - @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); - } else { - var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); - switch (val) { - inline else => |pl, tag| try container.fieldArbitraryDepth( - @tagName(tag), - pl, + 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(); - }, - .optional => if (val) |inner| { - try self.valueArbitraryDepth(inner, options); - } else { - try self.writer.writeAll("null"); - }, + } + try container.finish(); + }, + .@"union" => |@"union"| if (@"union".tag_type == null) { + @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); + } else { + var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + switch (val) { + inline else => |pl, tag| try container.fieldArbitraryDepth( + @tagName(tag), + pl, + options, + ), + } + try container.finish(); + }, + .optional => if (val) |inner| { + try self.valueArbitraryDepth(inner, options); + } else { + try self.writer.writeAll("null"); + }, - else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), - } + else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), } + } - /// Serialize an integer. - pub fn int(self: *Self, val: anytype) Writer.Error!void { - try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); - } + /// Serialize an integer. + pub fn int(self: *Self, val: anytype) std.io.AnyWriter.Error!void { + try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); + } - /// Serialize a float. - pub fn float(self: *Self, val: anytype) Writer.Error!void { - switch (@typeInfo(@TypeOf(val))) { - .float, .comptime_float => if (std.math.isNan(val)) { - return self.writer.writeAll("nan"); - } else if (@as(f128, val) == std.math.inf(f128)) { - return self.writer.writeAll("inf"); - } else if (@as(f128, val) == -std.math.inf(f128)) { - return self.writer.writeAll("-inf"); - } else { - try std.fmt.format(self.writer, "{d}", .{val}); - }, - else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), - } + /// Serialize a float. + pub fn float(self: *Self, val: anytype) std.io.AnyWriter.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .float, .comptime_float => if (std.math.isNan(val)) { + return self.writer.writeAll("nan"); + } else if (@as(f128, val) == std.math.inf(f128)) { + return self.writer.writeAll("inf"); + } else if (@as(f128, val) == -std.math.inf(f128)) { + return self.writer.writeAll("-inf"); + } else { + try std.fmt.format(self.writer, "{d}", .{val}); + }, + else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), } + } - fn identNeedsEscape(name: []const u8) bool { - std.debug.assert(name.len != 0); - for (name, 0..) |c, i| { - switch (c) { - 'A'...'Z', 'a'...'z', '_' => {}, - '0'...'9' => if (i == 0) return true, - else => return true, - } + fn identNeedsEscape(name: []const u8) bool { + std.debug.assert(name.len != 0); + for (name, 0..) |c, i| { + switch (c) { + 'A'...'Z', 'a'...'z', '_' => {}, + '0'...'9' => if (i == 0) return true, + else => return true, } - return std.zig.Token.keywords.has(name); } + return std.zig.Token.keywords.has(name); + } - /// Serialize `name` as an identifier. - /// - /// Escapes the identifier if necessary. - pub fn ident(self: *Self, name: []const u8) Writer.Error!void { - if (identNeedsEscape(name)) { - try self.writer.writeAll("@\""); - try self.writer.writeAll(name); - try self.writer.writeByte('"'); - } else { - try self.writer.writeAll(name); - } + /// Serialize `name` as an identifier. + /// + /// Escapes the identifier if necessary. + pub fn ident(self: *Self, name: []const u8) std.io.AnyWriter.Error!void { + if (identNeedsEscape(name)) { + try self.writer.writeAll("@\""); + try self.writer.writeAll(name); + try self.writer.writeByte('"'); + } else { + try self.writer.writeAll(name); } + } - /// Serialize `val` as a UTF8 codepoint. - /// - /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. - pub fn utf8Codepoint(self: *Self, val: u21) (Writer.Error || error{InvalidCodepoint})!void { - var buf: [8]u8 = undefined; - const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; - const str = buf[0..len]; - try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); - } + /// Serialize `val` as a UTF8 codepoint. + /// + /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. + pub fn utf8Codepoint( + self: *Self, + val: u21, + ) (std.io.AnyWriter.Error || error{InvalidCodepoint})!void { + var buf: [8]u8 = undefined; + const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; + const str = buf[0..len]; + try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); + } - /// Like `value`, but always serializes `val` as a slice. - /// - /// Will fail at comptime if `val` is not an array or slice. - pub fn slice(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - try self.sliceArbitraryDepth(val, options); - } + /// Like `value`, but always serializes `val` as a slice. + /// + /// Will fail at comptime if `val` is not an array or slice. + pub fn slice(self: *Self, val: anytype, options: ValueOptions) std.io.AnyWriter.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.sliceArbitraryDepth(val, options); + } - /// Like `value`, but recursive types are allowed. - /// - /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. - pub fn sliceMaxDepth( - self: *Self, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try checkValueDepth(val, depth); - try self.sliceArbitraryDepth(val, options); - } + /// Like `value`, but recursive types are allowed. + /// + /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. + pub fn sliceMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { + try checkValueDepth(val, depth); + try self.sliceArbitraryDepth(val, options); + } - /// Like `value`, but recursive types are allowed. - /// - /// It is the caller's responsibility to ensure that `val` does not contain cycles. - pub fn sliceArbitraryDepth( - self: *Self, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.sliceImpl(val, options); - } + /// Like `value`, but recursive types are allowed. + /// + /// It is the caller's responsibility to ensure that `val` does not contain cycles. + pub fn sliceArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + try self.sliceImpl(val, options); + } - fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); - for (val) |item_val| { - try container.itemArbitraryDepth(item_val, options); - } - try container.finish(); + fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) std.io.AnyWriter.Error!void { + var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.itemArbitraryDepth(item_val, options); } + try container.finish(); + } - /// Like `value`, but always serializes `val` as a string. - pub fn string(self: *Self, val: []const u8) Writer.Error!void { - try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)}); - } + /// Like `value`, but always serializes `val` as a string. + pub fn string(self: *Self, val: []const u8) std.io.AnyWriter.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, - }; + /// 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; - } + /// 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, + ) (std.io.AnyWriter.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; } + return error.InnerCarriageReturn; } + } - if (!options.top_level) { - try self.newline(); - try self.indent(); - } + 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("\\\\"); - } + 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); + if (!options.top_level) { + try self.writer.writeByte('\n'); // Even if whitespace off + try self.indent(); } + } - /// 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); - } + /// Create a `Struct` for writing ZON structs field by field. + pub fn startStruct( + self: *Self, + options: SerializeContainerOptions, + ) std.io.AnyWriter.Error!Struct { + return Struct.start(self, options); + } - /// Creates a `Slice` for writing ZON slices item by item. - pub fn startSlice(self: *Self, options: SerializeContainerOptions) Writer.Error!Slice { - return Slice.start(self, options); - } + /// Creates a `Tuple` for writing ZON tuples field by field. + pub fn startTuple( + self: *Self, + options: SerializeContainerOptions, + ) std.io.AnyWriter.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); - } + /// Creates a `Slice` for writing ZON slices item by item. + pub fn startSlice( + self: *Self, + options: SerializeContainerOptions, + ) std.io.AnyWriter.Error!Slice { + return Slice.start(self, options); + } + + fn indent(self: *Self) std.io.AnyWriter.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 newline(self: *Self) std.io.AnyWriter.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 newlineOrSpace(self: *Self, len: usize) std.io.AnyWriter.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(' '); - } + fn space(self: *Self) std.io.AnyWriter.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte(' '); } + } - /// Writes ZON tuples field by field. - pub const Tuple = struct { - container: Container, + /// 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), - }; - } + fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.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; - } + /// Finishes serializing the tuple. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Tuple) std.io.AnyWriter.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 `value`. + pub fn field( + self: *Tuple, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.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, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(null, val, options, depth); - } + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. + pub fn fieldMaxDepth( + self: *Tuple, + val: anytype, + options: ValueOptions, + depth: usize, + ) 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); - } + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by + /// `valueArbitraryDepth`. + pub fn fieldArbitraryDepth( + self: *Tuple, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.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); - } - }; + /// 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) std.io.AnyWriter.Error!void { + try self.container.fieldPrefix(null); + } + }; - /// Writes ZON structs field by field. - pub const Struct = struct { - container: Container, + /// 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), - }; - } + fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.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; - } + /// Finishes serializing the struct. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Struct) std.io.AnyWriter.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 `value`. + pub fn field( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.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, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(name, val, options, depth); - } + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. + pub fn fieldMaxDepth( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) 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); - } + /// Serialize a field. Equivalent to calling `fieldPrefix` followed by + /// `valueArbitraryDepth`. + pub fn fieldArbitraryDepth( + self: *Struct, + name: []const u8, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.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); - } - }; + /// 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) std.io.AnyWriter.Error!void { + try self.container.fieldPrefix(name); + } + }; - /// Writes ZON slices field by field. - pub const Slice = struct { - container: Container, + /// Writes ZON slices field by field. + pub const Slice = struct { + container: Container, - fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Slice { - try parent.writer.writeByte('&'); - return .{ - .container = try Container.start(parent, .anon, options), - }; - } + fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.Error!Slice { + try parent.writer.writeByte('&'); + return .{ + .container = try Container.start(parent, .anon, options), + }; + } - /// Finishes serializing the slice. - /// - /// Prints a trailing comma as configured when appropriate, and the closing bracket. - pub fn finish(self: *Slice) Writer.Error!void { - try self.container.finish(); - self.* = undefined; - } + /// Finishes serializing the slice. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Slice) std.io.AnyWriter.Error!void { + try self.container.finish(); + self.* = undefined; + } - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. - pub fn item( - self: *Slice, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.container.field(null, val, options); - } + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. + pub fn item( + self: *Slice, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + try self.container.field(null, val, options); + } - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. - pub fn itemMaxDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(null, val, options, depth); - } + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. + pub fn itemMaxDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { + try self.container.fieldMaxDepth(null, val, options, depth); + } - /// Serialize an item. Equivalent to calling `itemPrefix` followed by - /// `valueArbitraryDepth`. - pub fn itemArbitraryDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.container.fieldArbitraryDepth(null, val, options); - } + /// Serialize an item. Equivalent to calling `itemPrefix` followed by + /// `valueArbitraryDepth`. + pub fn itemArbitraryDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + try self.container.fieldArbitraryDepth(null, val, options); + } - /// Print a field prefix. This prints any necessary commas, and whitespace as - /// configured. Useful if you want to serialize the item value yourself. - pub fn itemPrefix(self: *Slice) Writer.Error!void { - try self.container.fieldPrefix(null); - } - }; + /// Print a field prefix. This prints any necessary commas, and whitespace as + /// configured. Useful if you want to serialize the item value yourself. + pub fn itemPrefix(self: *Slice) std.io.AnyWriter.Error!void { + try self.container.fieldPrefix(null); + } + }; - const Container = struct { - const FieldStyle = enum { named, anon }; + const Container = struct { + const FieldStyle = enum { named, anon }; + serializer: *Self, + field_style: FieldStyle, + options: SerializeContainerOptions, + empty: bool, + + fn start( serializer: *Self, field_style: FieldStyle, options: SerializeContainerOptions, - empty: bool, - - fn start( - serializer: *Self, - field_style: FieldStyle, - options: SerializeContainerOptions, - ) Writer.Error!Container { - if (options.shouldWrap()) serializer.indent_level +|= 1; - try serializer.writer.writeAll(".{"); - return .{ - .serializer = serializer, - .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; - } + ) std.io.AnyWriter.Error!Container { + if (options.shouldWrap()) serializer.indent_level +|= 1; + try serializer.writer.writeAll(".{"); + return .{ + .serializer = serializer, + .field_style = field_style, + .options = options, + .empty = true, + }; + } - fn fieldPrefix(self: *Container, name: ?[]const u8) Writer.Error!void { - if (!self.empty) { - try self.serializer.writer.writeByte(','); - } - self.empty = false; + fn finish(self: *Container) std.io.AnyWriter.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(); } - if (self.options.shouldWrap()) try self.serializer.indent(); - if (name) |n| { - try self.serializer.writer.writeByte('.'); - try self.serializer.ident(n); - try self.serializer.space(); - try self.serializer.writer.writeByte('='); - try self.serializer.space(); - } } + try self.serializer.writer.writeByte('}'); + self.* = undefined; + } - fn field( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - try self.fieldArbitraryDepth(name, val, options); + fn fieldPrefix(self: *Container, name: ?[]const u8) std.io.AnyWriter.Error!void { + if (!self.empty) { + try self.serializer.writer.writeByte(','); } - - fn fieldMaxDepth( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try checkValueDepth(val, depth); - try self.fieldArbitraryDepth(name, val, options); + self.empty = false; + if (self.options.shouldWrap()) { + try self.serializer.newline(); + } else if (!self.shouldElideSpaces()) { + try self.serializer.space(); } - - fn fieldArbitraryDepth( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.fieldPrefix(name); - try self.serializer.valueArbitraryDepth(val, options); + if (self.options.shouldWrap()) try self.serializer.indent(); + if (name) |n| { + try self.serializer.writer.writeByte('.'); + try self.serializer.ident(n); + try self.serializer.space(); + try self.serializer.writer.writeByte('='); + try self.serializer.space(); } + } - fn shouldElideSpaces(self: *const Container) bool { - return switch (self.options.whitespace_style) { - .fields => |fields| self.field_style != .named and fields == 1, - else => false, - }; - } - }; + fn field( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.fieldArbitraryDepth(name, val, options); + } - fn comptimeAssertNoRecursion(comptime T: type) void { - if (comptime typeIsRecursive(T)) { - @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); - } + fn fieldMaxDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) ExceededMaxDepth!void { + try checkValueDepth(val, depth); + try self.fieldArbitraryDepth(name, val, options); + } + + fn fieldArbitraryDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) std.io.AnyWriter.Error!void { + try self.fieldPrefix(name); + try self.serializer.valueArbitraryDepth(val, options); + } + + fn shouldElideSpaces(self: *const Container) bool { + return switch (self.options.whitespace_style) { + .fields => |fields| self.field_style != .named and fields == 1, + else => false, + }; } }; -} + + fn comptimeAssertNoRecursion(comptime T: type) void { + if (comptime typeIsRecursive(T)) { + @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); + } + } +}; fn expectSerializeEqual( expected: []const u8, @@ -938,7 +948,7 @@ fn expectSerializeEqual( ) !void { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - try serialize(value, options, buf.writer()); + try serialize(value, options, buf.writer().any()); try std.testing.expectEqualStrings(expected, buf.items); } @@ -1032,8 +1042,7 @@ test "std.zon stringify whitespace, high level API" { test "std.zon stringify whitespace, low level API" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); - const writer = buffer.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buffer.writer().any(), .{}); inline for (.{ true, false }) |whitespace| { serializer.options = .{ .whitespace = whitespace }; @@ -1390,8 +1399,7 @@ test "std.zon stringify whitespace, low level API" { test "std.zon stringify utf8 codepoints" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); - const writer = buffer.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buffer.writer().any(), .{}); // Minimal case try serializer.utf8Codepoint('a'); @@ -1481,8 +1489,7 @@ test "std.zon stringify utf8 codepoints" { test "std.zon stringify strings" { var buffer = std.ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); - const writer = buffer.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buffer.writer().any(), .{}); // Minimal case try serializer.string("abcâš¡\n"); @@ -1552,8 +1559,7 @@ test "std.zon stringify strings" { test "std.zon stringify multiline strings" { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - const writer = buf.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buf.writer().any(), .{}); inline for (.{ true, false }) |whitespace| { serializer.options.whitespace = whitespace; @@ -1777,18 +1783,18 @@ test "std.zon depth limits" { const Recurse = struct { r: []const @This() }; // Normal operation - try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer(), 16); + try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer().any(), 16); try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); buf.clearRetainingCapacity(); - try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer()); + try serializeArbitraryDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer().any()); try std.testing.expectEqualStrings(".{ 1, .{ 2, 3 } }", buf.items); buf.clearRetainingCapacity(); // Max depth failing on non recursive type try std.testing.expectError( - error.MaxDepth, - serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3), + error.ExceededMaxDepth, + serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer().any(), 3), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1796,7 +1802,7 @@ test "std.zon depth limits" { // Max depth passing on recursive type { const maybe_recurse = Recurse{ .r = &.{} }; - try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1804,7 +1810,7 @@ test "std.zon depth limits" { // Unchecked passing on recursive type { const maybe_recurse = Recurse{ .r = &.{} }; - try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); + try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer().any()); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1814,8 +1820,8 @@ test "std.zon depth limits" { var maybe_recurse = Recurse{ .r = &.{} }; maybe_recurse.r = &.{.{ .r = &.{} }}; try std.testing.expectError( - error.MaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1827,17 +1833,16 @@ test "std.zon depth limits" { const maybe_recurse: []const Recurse = &temp; try std.testing.expectError( - error.MaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - const writer = buf.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buf.writer().any(), .{}); try std.testing.expectError( - error.MaxDepth, + error.ExceededMaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 2), ); try std.testing.expectEqualStrings("", buf.items); @@ -1853,12 +1858,11 @@ test "std.zon depth limits" { var temp: [1]Recurse = .{.{ .r = &.{} }}; const maybe_recurse: []const Recurse = &temp; - try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); - const writer = buf.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buf.writer().any(), .{}); try serializer.sliceMaxDepth(maybe_recurse, .{}, 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); @@ -1876,16 +1880,15 @@ test "std.zon depth limits" { const maybe_recurse: []const Recurse = &temp; try std.testing.expectError( - error.MaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128), + error.ExceededMaxDepth, + serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 128), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - const writer = buf.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buf.writer().any(), .{}); try std.testing.expectError( - error.MaxDepth, + error.ExceededMaxDepth, serializer.sliceMaxDepth(maybe_recurse, .{}, 128), ); try std.testing.expectEqualStrings("", buf.items); @@ -1894,32 +1897,31 @@ test "std.zon depth limits" { // Max depth on other parts of the lower level API { - const writer = buf.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var serializer = Serializer.init(buf.writer().any(), .{}); const maybe_recurse: []const Recurse = &.{}; - try std.testing.expectError(error.MaxDepth, serializer.valueMaxDepth(1, .{}, 0)); + try std.testing.expectError(error.ExceededMaxDepth, serializer.valueMaxDepth(1, .{}, 0)); try serializer.valueMaxDepth(2, .{}, 1); try serializer.value(3, .{}); try serializer.valueArbitraryDepth(maybe_recurse, .{}); var s = try serializer.startStruct(.{}); - try std.testing.expectError(error.MaxDepth, s.fieldMaxDepth("a", 1, .{}, 0)); + 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 serializer.startTuple(.{}); - try std.testing.expectError(error.MaxDepth, t.fieldMaxDepth(1, .{}, 0)); + 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 serializer.startSlice(.{}); - try std.testing.expectError(error.MaxDepth, a.itemMaxDepth(1, .{}, 0)); + try std.testing.expectError(error.ExceededMaxDepth, a.itemMaxDepth(1, .{}, 0)); try a.itemMaxDepth(8, .{}, 1); try a.item(9, .{}); try a.itemArbitraryDepth(maybe_recurse, .{}); @@ -2035,42 +2037,41 @@ test "std.zon stringify primitives" { } test "std.zon stringify ident" { - var buffer = std.ArrayList(u8).init(std.testing.allocator); - defer buffer.deinit(); - const writer = buffer.writer(); - var serializer: Serializer(@TypeOf(writer)) = .init(writer, .{}); + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var serializer = Serializer.init(buf.writer().any(), .{}); try serializer.ident("a"); - try std.testing.expectEqualStrings("a", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("a", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("foo_1"); - try std.testing.expectEqualStrings("foo_1", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("foo_1", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("_foo_1"); - try std.testing.expectEqualStrings("_foo_1", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("_foo_1", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("foo bar"); - try std.testing.expectEqualStrings("@\"foo bar\"", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("@\"foo bar\"", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("1foo"); - try std.testing.expectEqualStrings("@\"1foo\"", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("@\"1foo\"", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("var"); - try std.testing.expectEqualStrings("@\"var\"", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("@\"var\"", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("true"); - try std.testing.expectEqualStrings("true", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("true", buf.items); + buf.clearRetainingCapacity(); try serializer.ident("_"); - try std.testing.expectEqualStrings("_", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings("_", buf.items); + buf.clearRetainingCapacity(); const Enum = enum { @"foo bar", From 5071a9a15b7ceb1fa078dc5c05862e7b6f7f1171 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 23:06:58 -0800 Subject: [PATCH 49/51] Replaces comment about keeping code in sync with struct that can be renamed --- lib/std/zon/parse.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index c4a4c097b097..87ec7c31a8c3 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -381,7 +381,7 @@ pub fn fromZoirNode( } fn requiresAllocator(comptime T: type) bool { - // Keep in sync with free, stringify, and requiresAllocator. + _ = valid_types; return switch (@typeInfo(T)) { .pointer => true, .array => |array| requiresAllocator(array.child), @@ -432,7 +432,7 @@ test "std.zon requiresAllocator" { pub fn free(gpa: Allocator, value: anytype) void { const Value = @TypeOf(value); - // Keep in sync with free, stringify, and requiresAllocator. + _ = valid_types; switch (@typeInfo(Value)) { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { @@ -470,13 +470,16 @@ pub fn free(gpa: Allocator, value: anytype) void { } } +/// Rename when adding or removing support for a type. +const valid_types = {}; + fn parseExpr( self: *@This(), comptime T: type, comptime options: Options, node: Zoir.Node.Index, ) !T { - // Keep in sync with free, stringify, and requiresAllocator. + _ = valid_types; switch (@typeInfo(T)) { .bool => return self.parseBool(node), .int => return self.parseInt(T, node), From 891f5864a4e3f69103baf50add473c0b826e55f4 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 9 Jan 2025 17:30:55 -0800 Subject: [PATCH 50/51] Trying out different serialize API --- lib/std/fmt.zig | 1 - lib/std/zon/stringify.zig | 1607 +++++++++++++++++++------------------ 2 files changed, 806 insertions(+), 802 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index e6bf94fdf4ed..2b5e78975e97 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1581,7 +1581,6 @@ test parseInt { try std.testing.expectEqual(@as(i5, -16), try std.fmt.parseInt(i5, "-10", 16)); } -/// Like `parseIntWithGenericCharacter`, but with a sign argument. fn parseIntWithSign( comptime Result: type, comptime Character: type, diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 1b7341d0b2d8..8cf5ee5702f3 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -44,12 +44,12 @@ pub const SerializeOptions = struct { pub fn serialize( val: anytype, comptime options: SerializeOptions, - writer: std.io.AnyWriter, + writer: anytype, ) @TypeOf(writer).Error!void { - var serializer = Serializer.init(writer, .{ + var sz = serializer(writer, .{ .whitespace = options.whitespace, }); - try serializer.value(val, .{ + try sz.value(val, .{ .emit_utf8_codepoints = options.emit_utf8_codepoints, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, @@ -62,13 +62,13 @@ pub fn serialize( pub fn serializeMaxDepth( val: anytype, comptime options: SerializeOptions, - writer: std.io.AnyWriter, + writer: anytype, depth: usize, -) Serializer.ExceededMaxDepth!void { - var serializer = Serializer.init(writer, .{ +) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void { + var sz = serializer(writer, .{ .whitespace = options.whitespace, }); - try serializer.valueMaxDepth(val, .{ + try sz.valueMaxDepth(val, .{ .emit_utf8_codepoints = options.emit_utf8_codepoints, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, @@ -81,12 +81,12 @@ pub fn serializeMaxDepth( pub fn serializeArbitraryDepth( val: anytype, comptime options: SerializeOptions, - writer: std.io.AnyWriter, + writer: anytype, ) @TypeOf(writer).Error!void { - var serializer = Serializer.init(writer, .{ + var sz = serializer(writer, .{ .whitespace = options.whitespace, }); - try serializer.valueArbitraryDepth(val, .{ + try sz.valueArbitraryDepth(val, .{ .emit_utf8_codepoints = options.emit_utf8_codepoints, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, @@ -284,662 +284,667 @@ pub const SerializeContainerOptions = struct { /// /// # Example /// ```zig -/// var serializer = Serializer.init(writer, .{}); -/// var vec2 = try serializer.startStruct(.{}); +/// var sz = serializer(writer, .{}); +/// var vec2 = try sz.startStruct(.{}); /// try vec2.field("x", 1.5, .{}); /// try vec2.fieldPrefix(); -/// try serializer.value(2.5); +/// try sz.value(2.5); /// try vec2.finish(); /// ``` -pub const Serializer = struct { - const Self = @This(); +pub fn Serializer(Writer: type) type { + return struct { + const Self = @This(); - pub const ExceededMaxDepth = error{ExceededMaxDepth} || std.io.AnyWriter.Error; + options: SerializerOptions, + indent_level: u8, + writer: Writer, - options: SerializerOptions, - indent_level: u8, - writer: std.io.AnyWriter, - - /// Initialize a serializer. - fn init(writer: std.io.AnyWriter, options: SerializerOptions) Self { - return .{ - .options = options, - .writer = writer, - .indent_level = 0, - }; - } + /// 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) std.io.AnyWriter.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - return self.valueArbitraryDepth(val, options); - } + /// Serialize a value, similar to `serialize`. + pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + return self.valueArbitraryDepth(val, options); + } - /// Serialize a value, similar to `serializeMaxDepth`. - pub fn valueMaxDepth( - self: *Self, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try checkValueDepth(val, depth); - 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, - ) std.io.AnyWriter.Error!void { - switch (@typeInfo(@TypeOf(val))) { - .int => |int_info| if (options.emit_utf8_codepoints and - int_info.signedness == .unsigned and - int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) - { - self.utf8Codepoint(val) catch |err| switch (err) { - error.InvalidCodepoint => unreachable, // Already validated - else => |e| return e, - }; - } else { - try self.int(val); - }, - .comptime_int => if (options.emit_utf8_codepoints and - val > 0 and - val <= std.math.maxInt(u21) and - std.unicode.utf8ValidCodepoint(val)) - { - self.utf8Codepoint(val) catch |err| switch (err) { - error.InvalidCodepoint => unreachable, // Already validated - else => |e| return e, - }; - } else { - try self.int(val); - }, - .float, .comptime_float => try self.float(val), - .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), - .enum_literal => { - try self.writer.writeByte('.'); - try self.ident(@tagName(val)); - }, - .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { - try self.writer.writeByte('.'); - try self.ident(@tagName(val)); - } else { - @compileError( - @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", - ); - }, - .void => try self.writer.writeAll("{}"), - .pointer => |pointer| { - const child_type = switch (@typeInfo(pointer.child)) { - .array => |array| array.child, - else => if (pointer.size != .Slice) @compileError( - @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", - ) else pointer.child, - }; - if (child_type == u8 and !options.emit_strings_as_containers) { - try self.string(val); + /// Serialize a value, similar to `serializeArbitraryDepth`. + pub fn valueArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .int => |int_info| if (options.emit_utf8_codepoints and + int_info.signedness == .unsigned and + int_info.bits <= 21 and std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; } else { - try self.sliceImpl(val, options); - } - }, - .array => { - var container = try self.startTuple( - .{ .whitespace_style = .{ .fields = val.len } }, - ); - for (val) |item_val| { - try container.fieldArbitraryDepth(item_val, options); - } - try container.finish(); - }, - .@"struct" => |@"struct"| if (@"struct".is_tuple) { - var container = try self.startTuple( - .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, - ); - inline for (val) |field_value| { - try container.fieldArbitraryDepth(field_value, options); - } - try container.finish(); - } else { - // Decide which fields to emit - const fields, const skipped = if (options.emit_default_optional_fields) b: { - break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; - } else b: { - var fields = @"struct".fields.len; - var skipped = [1]bool{false} ** @"struct".fields.len; - inline for (@"struct".fields, &skipped) |field_info, *skip| { - if (field_info.default_value) |default_field_value_opaque| { - const field_value = @field(val, field_info.name); - const default_field_value: *const @TypeOf(field_value) = @ptrCast( - @alignCast(default_field_value_opaque), - ); - if (std.meta.eql(field_value, default_field_value.*)) { - skip.* = true; - fields -= 1; + try self.int(val); + }, + .comptime_int => if (options.emit_utf8_codepoints and + val > 0 and + val <= std.math.maxInt(u21) and + std.unicode.utf8ValidCodepoint(val)) + { + self.utf8Codepoint(val) catch |err| switch (err) { + error.InvalidCodepoint => unreachable, // Already validated + else => |e| return e, + }; + } else { + try self.int(val); + }, + .float, .comptime_float => try self.float(val), + .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), + .enum_literal => { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + }, + .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { + try self.writer.writeByte('.'); + try self.ident(@tagName(val)); + } else { + @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", + ); + }, + .void => try self.writer.writeAll("{}"), + .pointer => |pointer| { + const child_type = switch (@typeInfo(pointer.child)) { + .array => |array| array.child, + else => if (pointer.size != .Slice) @compileError( + @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", + ) else pointer.child, + }; + if (child_type == u8 and !options.emit_strings_as_containers) { + try self.string(val); + } else { + try self.sliceImpl(val, options); + } + }, + .array => { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = val.len } }, + ); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = @"struct".fields.len } }, + ); + inline for (val) |field_value| { + try container.fieldArbitraryDepth(field_value, options); + } + try container.finish(); + } else { + // Decide which fields to emit + const fields, const skipped = if (options.emit_default_optional_fields) b: { + break :b .{ @"struct".fields.len, [1]bool{false} ** @"struct".fields.len }; + } else b: { + var fields = @"struct".fields.len; + var skipped = [1]bool{false} ** @"struct".fields.len; + inline for (@"struct".fields, &skipped) |field_info, *skip| { + if (field_info.default_value) |default_field_value_opaque| { + const field_value = @field(val, field_info.name); + const default_field_value: *const @TypeOf(field_value) = @ptrCast( + @alignCast(default_field_value_opaque), + ); + if (std.meta.eql(field_value, default_field_value.*)) { + skip.* = true; + fields -= 1; + } } } + break :b .{ fields, skipped }; + }; + + // Emit those fields + var container = try self.startStruct( + .{ .whitespace_style = .{ .fields = fields } }, + ); + inline for (@"struct".fields, skipped) |field_info, skip| { + if (!skip) { + try container.fieldArbitraryDepth( + field_info.name, + @field(val, field_info.name), + options, + ); + } } - 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), + try container.finish(); + }, + .@"union" => |@"union"| if (@"union".tag_type == null) { + @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); + } else { + var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + switch (val) { + inline else => |pl, tag| try container.fieldArbitraryDepth( + @tagName(tag), + pl, options, - ); + ), } - } - try container.finish(); - }, - .@"union" => |@"union"| if (@"union".tag_type == null) { - @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); - } else { - var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); - switch (val) { - inline else => |pl, tag| try container.fieldArbitraryDepth( - @tagName(tag), - pl, - options, - ), - } - try container.finish(); - }, - .optional => if (val) |inner| { - try self.valueArbitraryDepth(inner, options); - } else { - try self.writer.writeAll("null"); - }, + try container.finish(); + }, + .optional => if (val) |inner| { + try self.valueArbitraryDepth(inner, options); + } else { + try self.writer.writeAll("null"); + }, - else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), + else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), + } } - } - /// Serialize an integer. - pub fn int(self: *Self, val: anytype) std.io.AnyWriter.Error!void { - try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); - } - - /// Serialize a float. - pub fn float(self: *Self, val: anytype) std.io.AnyWriter.Error!void { - switch (@typeInfo(@TypeOf(val))) { - .float, .comptime_float => if (std.math.isNan(val)) { - return self.writer.writeAll("nan"); - } else if (@as(f128, val) == std.math.inf(f128)) { - return self.writer.writeAll("inf"); - } else if (@as(f128, val) == -std.math.inf(f128)) { - return self.writer.writeAll("-inf"); - } else { - try std.fmt.format(self.writer, "{d}", .{val}); - }, - else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), + /// Serialize an integer. + pub fn int(self: *Self, val: anytype) Writer.Error!void { + try std.fmt.formatInt(val, 10, .lower, .{}, self.writer); } - } - fn identNeedsEscape(name: []const u8) bool { - std.debug.assert(name.len != 0); - for (name, 0..) |c, i| { - switch (c) { - 'A'...'Z', 'a'...'z', '_' => {}, - '0'...'9' => if (i == 0) return true, - else => return true, + /// Serialize a float. + pub fn float(self: *Self, val: anytype) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .float, .comptime_float => if (std.math.isNan(val)) { + return self.writer.writeAll("nan"); + } else if (@as(f128, val) == std.math.inf(f128)) { + return self.writer.writeAll("inf"); + } else if (@as(f128, val) == -std.math.inf(f128)) { + return self.writer.writeAll("-inf"); + } else { + try std.fmt.format(self.writer, "{d}", .{val}); + }, + else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), } } - return std.zig.Token.keywords.has(name); - } - /// Serialize `name` as an identifier. - /// - /// Escapes the identifier if necessary. - pub fn ident(self: *Self, name: []const u8) std.io.AnyWriter.Error!void { - if (identNeedsEscape(name)) { - try self.writer.writeAll("@\""); - try self.writer.writeAll(name); - try self.writer.writeByte('"'); - } else { - try self.writer.writeAll(name); + fn identNeedsEscape(name: []const u8) bool { + std.debug.assert(name.len != 0); + for (name, 0..) |c, i| { + switch (c) { + 'A'...'Z', 'a'...'z', '_' => {}, + '0'...'9' => if (i == 0) return true, + else => return true, + } + } + return std.zig.Token.keywords.has(name); } - } - /// Serialize `val` as a UTF8 codepoint. - /// - /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. - pub fn utf8Codepoint( - self: *Self, - val: u21, - ) (std.io.AnyWriter.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)}); - } + /// Serialize `name` as an identifier. + /// + /// Escapes the identifier if necessary. + pub fn ident(self: *Self, name: []const u8) Writer.Error!void { + if (identNeedsEscape(name)) { + try self.writer.writeAll("@\""); + try self.writer.writeAll(name); + try self.writer.writeByte('"'); + } else { + try self.writer.writeAll(name); + } + } - /// Like `value`, but always serializes `val` as a slice. - /// - /// Will fail at comptime if `val` is not an array or slice. - pub fn slice(self: *Self, val: anytype, options: ValueOptions) std.io.AnyWriter.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - try self.sliceArbitraryDepth(val, options); - } + /// Serialize `val` as a UTF8 codepoint. + /// + /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. + pub fn utf8Codepoint( + self: *Self, + val: u21, + ) (Writer.Error || error{InvalidCodepoint})!void { + var buf: [8]u8 = undefined; + const len = std.unicode.utf8Encode(val, &buf) catch return error.InvalidCodepoint; + const str = buf[0..len]; + try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); + } + + /// Like `value`, but always serializes `val` as a slice. + /// + /// Will fail at comptime if `val` is not an array or slice. + pub fn slice(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.sliceArbitraryDepth(val, options); + } - /// Like `value`, but recursive types are allowed. - /// - /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. - pub fn sliceMaxDepth( - self: *Self, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try checkValueDepth(val, depth); - try self.sliceArbitraryDepth(val, options); - } + /// Like `value`, but recursive types are allowed. + /// + /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. + pub fn sliceMaxDepth( + self: *Self, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + try self.sliceArbitraryDepth(val, options); + } - /// Like `value`, but recursive types are allowed. - /// - /// It is the caller's responsibility to ensure that `val` does not contain cycles. - pub fn sliceArbitraryDepth( - self: *Self, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.sliceImpl(val, options); - } + /// Like `value`, but recursive types are allowed. + /// + /// It is the caller's responsibility to ensure that `val` does not contain cycles. + pub fn sliceArbitraryDepth( + self: *Self, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.sliceImpl(val, options); + } - fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) std.io.AnyWriter.Error!void { - var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); - for (val) |item_val| { - try container.itemArbitraryDepth(item_val, options); + fn sliceImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + var container = try self.startSlice(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.itemArbitraryDepth(item_val, options); + } + try container.finish(); } - try container.finish(); - } - /// Like `value`, but always serializes `val` as a string. - pub fn string(self: *Self, val: []const u8) std.io.AnyWriter.Error!void { - try std.fmt.format(self.writer, "\"{}\"", .{std.zig.fmtEscapes(val)}); - } + /// 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, - }; + /// 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, - ) (std.io.AnyWriter.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; + /// 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; } - return error.InnerCarriageReturn; } - } - if (!options.top_level) { - try self.newline(); - try self.indent(); - } + 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("\\\\"); + 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(); + 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, - ) std.io.AnyWriter.Error!Struct { - return Struct.start(self, options); - } - - /// Creates a `Tuple` for writing ZON tuples field by field. - pub fn startTuple( - self: *Self, - options: SerializeContainerOptions, - ) std.io.AnyWriter.Error!Tuple { - return Tuple.start(self, options); - } - - /// Creates a `Slice` for writing ZON slices item by item. - pub fn startSlice( - self: *Self, - options: SerializeContainerOptions, - ) std.io.AnyWriter.Error!Slice { - return Slice.start(self, options); - } - - fn indent(self: *Self) std.io.AnyWriter.Error!void { - if (self.options.whitespace) { - try self.writer.writeByteNTimes(' ', 4 * self.indent_level); + /// 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); } - } - fn newline(self: *Self) std.io.AnyWriter.Error!void { - if (self.options.whitespace) { - try self.writer.writeByte('\n'); + /// 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 newlineOrSpace(self: *Self, len: usize) std.io.AnyWriter.Error!void { - if (self.containerShouldWrap(len)) { - try self.newline(); - } else { - try self.space(); + /// Creates a `Slice` for writing ZON slices item by item. + pub fn startSlice( + self: *Self, + options: SerializeContainerOptions, + ) Writer.Error!Slice { + return Slice.start(self, options); } - } - fn space(self: *Self) std.io.AnyWriter.Error!void { - if (self.options.whitespace) { - try self.writer.writeByte(' '); + fn indent(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByteNTimes(' ', 4 * self.indent_level); + } } - } - - /// Writes ZON tuples field by field. - pub const Tuple = struct { - container: Container, - fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.Error!Tuple { - return .{ - .container = try Container.start(parent, .anon, options), - }; + fn newline(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte('\n'); + } } - /// Finishes serializing the tuple. - /// - /// Prints a trailing comma as configured when appropriate, and the closing bracket. - pub fn finish(self: *Tuple) std.io.AnyWriter.Error!void { - try self.container.finish(); - self.* = undefined; + fn newlineOrSpace(self: *Self, len: usize) Writer.Error!void { + if (self.containerShouldWrap(len)) { + try self.newline(); + } else { + try self.space(); + } } - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. - pub fn field( - self: *Tuple, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.field(null, val, options); + fn space(self: *Self) Writer.Error!void { + if (self.options.whitespace) { + try self.writer.writeByte(' '); + } } - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. - pub fn fieldMaxDepth( - self: *Tuple, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(null, val, options, depth); - } + /// Writes ZON tuples field by field. + pub const Tuple = struct { + container: Container, - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by - /// `valueArbitraryDepth`. - pub fn fieldArbitraryDepth( - self: *Tuple, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.fieldArbitraryDepth(null, val, options); - } + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Tuple { + return .{ + .container = try Container.start(parent, .anon, 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) std.io.AnyWriter.Error!void { - try self.container.fieldPrefix(null); - } - }; + /// 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; + } - /// Writes ZON structs field by field. - pub const Struct = struct { - container: Container, + /// 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); + } - fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.Error!Struct { - return .{ - .container = try Container.start(parent, .named, 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); + } - /// Finishes serializing the struct. - /// - /// Prints a trailing comma as configured when appropriate, and the closing bracket. - pub fn finish(self: *Struct) std.io.AnyWriter.Error!void { - try self.container.finish(); - self.* = undefined; - } + /// 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); + } - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`. - pub fn field( - self: *Struct, - name: []const u8, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.field(name, 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); + } + }; - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`. - pub fn fieldMaxDepth( - self: *Struct, - name: []const u8, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(name, val, options, depth); - } + /// Writes ZON structs field by field. + pub const Struct = struct { + container: Container, - /// Serialize a field. Equivalent to calling `fieldPrefix` followed by - /// `valueArbitraryDepth`. - pub fn fieldArbitraryDepth( - self: *Struct, - name: []const u8, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.fieldArbitraryDepth(name, val, options); - } + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Struct { + return .{ + .container = try Container.start(parent, .named, 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) std.io.AnyWriter.Error!void { - try self.container.fieldPrefix(name); - } - }; + /// 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; + } - /// Writes ZON slices field by field. - pub const Slice = struct { - container: Container, + /// 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); + } - fn start(parent: *Self, options: SerializeContainerOptions) std.io.AnyWriter.Error!Slice { - try parent.writer.writeByte('&'); - return .{ - .container = try Container.start(parent, .anon, 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); + } - /// Finishes serializing the slice. - /// - /// Prints a trailing comma as configured when appropriate, and the closing bracket. - pub fn finish(self: *Slice) std.io.AnyWriter.Error!void { - try self.container.finish(); - self.* = undefined; - } + /// 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); + } - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. - pub fn item( - self: *Slice, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.field(null, 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); + } + }; - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. - pub fn itemMaxDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - depth: usize, - ) ExceededMaxDepth!void { - try self.container.fieldMaxDepth(null, val, options, depth); - } + /// Writes ZON slices field by field. + pub const Slice = struct { + container: Container, - /// Serialize an item. Equivalent to calling `itemPrefix` followed by - /// `valueArbitraryDepth`. - pub fn itemArbitraryDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.container.fieldArbitraryDepth(null, val, options); - } + fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Slice { + try parent.writer.writeByte('&'); + return .{ + .container = try Container.start(parent, .anon, options), + }; + } - /// Print a field prefix. This prints any necessary commas, and whitespace as - /// configured. Useful if you want to serialize the item value yourself. - pub fn itemPrefix(self: *Slice) std.io.AnyWriter.Error!void { - try self.container.fieldPrefix(null); - } - }; + /// Finishes serializing the slice. + /// + /// Prints a trailing comma as configured when appropriate, and the closing bracket. + pub fn finish(self: *Slice) Writer.Error!void { + try self.container.finish(); + self.* = undefined; + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. + pub fn item( + self: *Slice, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.field(null, val, options); + } - const Container = struct { - const FieldStyle = enum { named, anon }; + /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. + pub fn itemMaxDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try self.container.fieldMaxDepth(null, val, options, depth); + } + + /// Serialize an item. Equivalent to calling `itemPrefix` followed by + /// `valueArbitraryDepth`. + pub fn itemArbitraryDepth( + self: *Slice, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + try self.container.fieldArbitraryDepth(null, val, options); + } + + /// Print a field prefix. This prints any necessary commas, and whitespace as + /// configured. Useful if you want to serialize the item value yourself. + pub fn itemPrefix(self: *Slice) Writer.Error!void { + try self.container.fieldPrefix(null); + } + }; - serializer: *Self, - field_style: FieldStyle, - options: SerializeContainerOptions, - empty: bool, + const Container = struct { + const FieldStyle = enum { named, anon }; - fn start( serializer: *Self, field_style: FieldStyle, options: SerializeContainerOptions, - ) std.io.AnyWriter.Error!Container { - if (options.shouldWrap()) serializer.indent_level +|= 1; - try serializer.writer.writeAll(".{"); - return .{ - .serializer = serializer, - .field_style = field_style, - .options = options, - .empty = true, - }; - } + 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) std.io.AnyWriter.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(','); + 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(); - try self.serializer.indent(); } else if (!self.shouldElideSpaces()) { try self.serializer.space(); } + if (self.options.shouldWrap()) try self.serializer.indent(); + if (name) |n| { + try self.serializer.writer.writeByte('.'); + try self.serializer.ident(n); + try self.serializer.space(); + try self.serializer.writer.writeByte('='); + try self.serializer.space(); + } } - try self.serializer.writer.writeByte('}'); - self.* = undefined; - } - fn fieldPrefix(self: *Container, name: ?[]const u8) std.io.AnyWriter.Error!void { - if (!self.empty) { - try self.serializer.writer.writeByte(','); - } - self.empty = false; - if (self.options.shouldWrap()) { - try self.serializer.newline(); - } else if (!self.shouldElideSpaces()) { - try self.serializer.space(); - } - if (self.options.shouldWrap()) try self.serializer.indent(); - if (name) |n| { - try self.serializer.writer.writeByte('.'); - try self.serializer.ident(n); - try self.serializer.space(); - try self.serializer.writer.writeByte('='); - try self.serializer.space(); + fn field( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + ) Writer.Error!void { + comptimeAssertNoRecursion(@TypeOf(val)); + try self.fieldArbitraryDepth(name, val, options); } - } - fn field( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); - try self.fieldArbitraryDepth(name, val, options); - } + fn fieldMaxDepth( + self: *Container, + name: ?[]const u8, + val: anytype, + options: ValueOptions, + depth: usize, + ) (Writer.Error || error{ExceededMaxDepth})!void { + try checkValueDepth(val, depth); + try self.fieldArbitraryDepth(name, val, options); + } - fn fieldMaxDepth( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - depth: usize, - ) 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 fieldArbitraryDepth( - self: *Container, - name: ?[]const u8, - val: anytype, - options: ValueOptions, - ) std.io.AnyWriter.Error!void { - try self.fieldPrefix(name); - try self.serializer.valueArbitraryDepth(val, options); - } + fn shouldElideSpaces(self: *const Container) bool { + return switch (self.options.whitespace_style) { + .fields => |fields| self.field_style != .named and fields == 1, + else => false, + }; + } + }; - fn shouldElideSpaces(self: *const Container) bool { - return switch (self.options.whitespace_style) { - .fields => |fields| self.field_style != .named and fields == 1, - else => false, - }; + fn comptimeAssertNoRecursion(comptime T: type) void { + if (comptime typeIsRecursive(T)) { + @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); + } } }; +} - fn comptimeAssertNoRecursion(comptime T: type) void { - if (comptime typeIsRecursive(T)) { - @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); - } - } -}; +/// Creates a new `Serializer` with the given writer and options. +pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeOf(writer)) { + return .init(writer, options); +} fn expectSerializeEqual( expected: []const u8, @@ -948,7 +953,7 @@ fn expectSerializeEqual( ) !void { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - try serialize(value, options, buf.writer().any()); + try serialize(value, options, buf.writer()); try std.testing.expectEqualStrings(expected, buf.items); } @@ -1040,59 +1045,59 @@ test "std.zon stringify whitespace, high level API" { } test "std.zon stringify whitespace, low level API" { - var buffer = std.ArrayList(u8).init(std.testing.allocator); - defer buffer.deinit(); - var serializer = Serializer.init(buffer.writer().any(), .{}); + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); inline for (.{ true, false }) |whitespace| { - serializer.options = .{ .whitespace = whitespace }; + sz.options = .{ .whitespace = whitespace }; // Empty containers { - var container = try serializer.startStruct(.{}); + var container = try sz.startStruct(.{}); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{}); + var container = try sz.startTuple(.{}); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + var container = try sz.startStruct(.{ .whitespace_style = .{ .wrap = false } }); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 0 } }); + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 0 } }); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 0 } }); + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 0 } }); try container.finish(); - try std.testing.expectEqualStrings(".{}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{}", buf.items); + buf.clearRetainingCapacity(); } // Size 1 { - var container = try serializer.startStruct(.{}); + var container = try sz.startStruct(.{}); try container.field("a", 1, .{}); try container.finish(); if (whitespace) { @@ -1100,15 +1105,15 @@ test "std.zon stringify whitespace, low level API" { \\.{ \\ .a = 1, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{}); + var container = try sz.startTuple(.{}); try container.field(1, .{}); try container.finish(); if (whitespace) { @@ -1116,62 +1121,62 @@ test "std.zon stringify whitespace, low level API" { \\.{ \\ 1, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{1}", buffer.items); + try std.testing.expectEqualStrings(".{1}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { // We get extra spaces here, since we didn't know up front that there would only be one // field. - var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + var container = try sz.startTuple(.{ .whitespace_style = .{ .wrap = false } }); try container.field(1, .{}); try container.finish(); if (whitespace) { - try std.testing.expectEqualStrings(".{ 1 }", buffer.items); + try std.testing.expectEqualStrings(".{ 1 }", buf.items); } else { - try std.testing.expectEqualStrings(".{1}", buffer.items); + try std.testing.expectEqualStrings(".{1}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ .a = 1 }", buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 1 } }); + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 1 } }); try container.field(1, .{}); try container.finish(); - try std.testing.expectEqualStrings(".{1}", buffer.items); - buffer.clearRetainingCapacity(); + try std.testing.expectEqualStrings(".{1}", buf.items); + buf.clearRetainingCapacity(); } // Size 2 { - var container = try serializer.startStruct(.{}); + var container = try sz.startStruct(.{}); try container.field("a", 1, .{}); try container.field("b", 2, .{}); try container.finish(); @@ -1181,15 +1186,15 @@ test "std.zon stringify whitespace, low level API" { \\ .a = 1, \\ .b = 2, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{}); + var container = try sz.startTuple(.{}); try container.field(1, .{}); try container.field(2, .{}); try container.finish(); @@ -1199,68 +1204,68 @@ test "std.zon stringify whitespace, low level API" { \\ 1, \\ 2, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{1,2}", buffer.items); + try std.testing.expectEqualStrings(".{1,2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); } else { - try std.testing.expectEqualStrings(".{1,2}", buffer.items); + try std.testing.expectEqualStrings(".{1,2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 2 } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2 }", buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 2 } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); } else { - try std.testing.expectEqualStrings(".{1,2}", buffer.items); + try std.testing.expectEqualStrings(".{1,2}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } // Size 3 { - var container = try serializer.startStruct(.{}); + var container = try sz.startStruct(.{}); try container.field("a", 1, .{}); try container.field("b", 2, .{}); try container.field("c", 3, .{}); @@ -1272,15 +1277,15 @@ test "std.zon stringify whitespace, low level API" { \\ .b = 2, \\ .c = 3, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{}); + var container = try sz.startTuple(.{}); try container.field(1, .{}); try container.field(2, .{}); try container.field(3, .{}); @@ -1292,43 +1297,43 @@ test "std.zon stringify whitespace, low level API" { \\ 2, \\ 3, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ .a = 1, .b = 2, .c = 3 }", buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .wrap = false } }); + 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 }", buffer.items); + try std.testing.expectEqualStrings(".{ 1, 2, 3 }", buf.items); } else { - try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .fields = 3 } }); + var container = try sz.startStruct(.{ .whitespace_style = .{ .fields = 3 } }); try container.field("a", 1, .{}); try container.field("b", 2, .{}); try container.field("c", 3, .{}); @@ -1340,15 +1345,15 @@ test "std.zon stringify whitespace, low level API" { \\ .b = 2, \\ .c = 3, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buffer.items); + try std.testing.expectEqualStrings(".{.a=1,.b=2,.c=3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } { - var container = try serializer.startTuple(.{ .whitespace_style = .{ .fields = 3 } }); + var container = try sz.startTuple(.{ .whitespace_style = .{ .fields = 3 } }); try container.field(1, .{}); try container.field(2, .{}); try container.field(3, .{}); @@ -1360,16 +1365,16 @@ test "std.zon stringify whitespace, low level API" { \\ 2, \\ 3, \\} - , buffer.items); + , buf.items); } else { - try std.testing.expectEqualStrings(".{1,2,3}", buffer.items); + try std.testing.expectEqualStrings(".{1,2,3}", buf.items); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } // Nested objects where the outer container doesn't wrap but the inner containers do { - var container = try serializer.startStruct(.{ .whitespace_style = .{ .wrap = false } }); + 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(); @@ -1384,119 +1389,119 @@ test "std.zon stringify whitespace, low level API" { \\ 5, \\ 6, \\} } - , buffer.items); + , buf.items); } else { try std.testing.expectEqualStrings( ".{.first=.{1,2,3},.second=.{4,5,6}}", - buffer.items, + buf.items, ); } - buffer.clearRetainingCapacity(); + buf.clearRetainingCapacity(); } } } test "std.zon stringify utf8 codepoints" { - var buffer = std.ArrayList(u8).init(std.testing.allocator); - defer buffer.deinit(); - var serializer = Serializer.init(buffer.writer().any(), .{}); + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); // Minimal case - try serializer.utf8Codepoint('a'); - try std.testing.expectEqualStrings("'a'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.utf8Codepoint('a'); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); - try serializer.int('a'); - try std.testing.expectEqualStrings("97", buffer.items); - buffer.clearRetainingCapacity(); + try sz.int('a'); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('a', .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings("'a'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('a', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'a'", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('a', .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings("97", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('a', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); // Short escaped codepoint - try serializer.utf8Codepoint('\n'); - try std.testing.expectEqualStrings("'\\n'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.utf8Codepoint('\n'); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); - try serializer.int('\n'); - try std.testing.expectEqualStrings("10", buffer.items); - buffer.clearRetainingCapacity(); + try sz.int('\n'); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('\n', .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings("'\\n'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('\n', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('\n', .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings("10", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('\n', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("10", buf.items); + buf.clearRetainingCapacity(); // Large codepoint - try serializer.utf8Codepoint('âš¡'); - try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.utf8Codepoint('âš¡'); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); - try serializer.int('âš¡'); - try std.testing.expectEqualStrings("9889", buffer.items); - buffer.clearRetainingCapacity(); + try sz.int('âš¡'); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('âš¡', .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('âš¡', .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); - try serializer.value('âš¡', .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings("9889", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value('âš¡', .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); // Invalid codepoint - try std.testing.expectError(error.InvalidCodepoint, serializer.utf8Codepoint(0x110000 + 1)); + try std.testing.expectError(error.InvalidCodepoint, sz.utf8Codepoint(0x110000 + 1)); - try serializer.int(0x110000 + 1); - try std.testing.expectEqualStrings("1114113", buffer.items); - buffer.clearRetainingCapacity(); + try sz.int(0x110000 + 1); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); - try serializer.value(0x110000 + 1, .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings("1114113", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); - try serializer.value(0x110000 + 1, .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings("1114113", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); // Valid codepoint, not a codepoint type - try serializer.value(@as(u22, 'a'), .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings("97", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(@as(u22, 'a'), .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); - try serializer.value(@as(i32, 'a'), .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings("97", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(@as(i32, 'a'), .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); // Make sure value options are passed to children - try serializer.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = true }); - try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = true }); + try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items); + buf.clearRetainingCapacity(); - try serializer.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = false }); - try std.testing.expectEqualStrings(".{ .c = 9889 }", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = false }); + try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items); + buf.clearRetainingCapacity(); } test "std.zon stringify strings" { - var buffer = std.ArrayList(u8).init(std.testing.allocator); - defer buffer.deinit(); - var serializer = Serializer.init(buffer.writer().any(), .{}); + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); // Minimal case - try serializer.string("abcâš¡\n"); - try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buffer.items); - buffer.clearRetainingCapacity(); + try sz.string("abcâš¡\n"); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); + buf.clearRetainingCapacity(); - try serializer.slice("abcâš¡\n", .{}); + try sz.slice("abcâš¡\n", .{}); try std.testing.expectEqualStrings( \\&.{ \\ 97, @@ -1507,14 +1512,14 @@ test "std.zon stringify strings" { \\ 161, \\ 10, \\} - , buffer.items); - buffer.clearRetainingCapacity(); + , buf.items); + buf.clearRetainingCapacity(); - try serializer.value("abcâš¡\n", .{}); - try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value("abcâš¡\n", .{}); + try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); + buf.clearRetainingCapacity(); - try serializer.value("abcâš¡\n", .{ .emit_strings_as_containers = true }); + try sz.value("abcâš¡\n", .{ .emit_strings_as_containers = true }); try std.testing.expectEqualStrings( \\&.{ \\ 97, @@ -1525,83 +1530,83 @@ test "std.zon stringify strings" { \\ 161, \\ 10, \\} - , buffer.items); - buffer.clearRetainingCapacity(); + , buf.items); + buf.clearRetainingCapacity(); // Value options are inherited by children - try serializer.value(.{ .str = "abc" }, .{}); - try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buffer.items); - buffer.clearRetainingCapacity(); + try sz.value(.{ .str = "abc" }, .{}); + try std.testing.expectEqualStrings(".{ .str = \"abc\" }", buf.items); + buf.clearRetainingCapacity(); - try serializer.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); + try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); try std.testing.expectEqualStrings( \\.{ .str = &.{ \\ 97, \\ 98, \\ 99, \\} } - , buffer.items); - buffer.clearRetainingCapacity(); + , buf.items); + buf.clearRetainingCapacity(); // Arrays (rather than pointers to arrays) of u8s are not considered strings, so that data can // round trip correctly. - try serializer.value("abc".*, .{}); + try sz.value("abc".*, .{}); try std.testing.expectEqualStrings( \\.{ \\ 97, \\ 98, \\ 99, \\} - , buffer.items); - buffer.clearRetainingCapacity(); + , buf.items); + buf.clearRetainingCapacity(); } test "std.zon stringify multiline strings" { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); inline for (.{ true, false }) |whitespace| { - serializer.options.whitespace = whitespace; + sz.options.whitespace = whitespace; { - try serializer.multilineString("", .{ .top_level = true }); + try sz.multilineString("", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("abcâš¡", .{ .top_level = true }); + try sz.multilineString("abcâš¡", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\abcâš¡", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("abcâš¡\ndef", .{ .top_level = true }); + try sz.multilineString("abcâš¡\ndef", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("abcâš¡\r\ndef", .{ .top_level = true }); + try sz.multilineString("abcâš¡\r\ndef", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\abcâš¡\n\\\\def", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("\nabcâš¡", .{ .top_level = true }); + try sz.multilineString("\nabcâš¡", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("\r\nabcâš¡", .{ .top_level = true }); + try sz.multilineString("\r\nabcâš¡", .{ .top_level = true }); try std.testing.expectEqualStrings("\\\\\n\\\\abcâš¡", buf.items); buf.clearRetainingCapacity(); } { - try serializer.multilineString("abc\ndef", .{}); + try sz.multilineString("abc\ndef", .{}); if (whitespace) { try std.testing.expectEqualStrings("\n\\\\abc\n\\\\def\n", buf.items); } else { @@ -1612,7 +1617,7 @@ test "std.zon stringify multiline strings" { { const str: []const u8 = &.{ 'a', '\r', 'c' }; - try serializer.string(str); + try sz.string(str); try std.testing.expectEqualStrings("\"a\\rc\"", buf.items); buf.clearRetainingCapacity(); } @@ -1620,15 +1625,15 @@ test "std.zon stringify multiline strings" { { try std.testing.expectError( error.InnerCarriageReturn, - serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c' }), .{}), ); try std.testing.expectError( error.InnerCarriageReturn, - serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\n' }), .{}), ); try std.testing.expectError( error.InnerCarriageReturn, - serializer.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), + sz.multilineString(@as([]const u8, &.{ 'a', '\r', 'c', '\r', '\n' }), .{}), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1783,18 +1788,18 @@ test "std.zon depth limits" { const Recurse = struct { r: []const @This() }; // Normal operation - try serializeMaxDepth(.{ 1, .{ 2, 3 } }, .{}, buf.writer().any(), 16); + 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().any()); + 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().any(), 3), + serializeMaxDepth(.{ 1, .{ 2, .{ 3, 4 } } }, .{}, buf.writer(), 3), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1802,7 +1807,7 @@ test "std.zon depth limits" { // Max depth passing on recursive type { const maybe_recurse = Recurse{ .r = &.{} }; - try serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1810,7 +1815,7 @@ test "std.zon depth limits" { // Unchecked passing on recursive type { const maybe_recurse = Recurse{ .r = &.{} }; - try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer().any()); + try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1821,7 +1826,7 @@ test "std.zon depth limits" { maybe_recurse.r = &.{.{ .r = &.{} }}; try std.testing.expectError( error.ExceededMaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2), + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1834,21 +1839,21 @@ test "std.zon depth limits" { try std.testing.expectError( error.ExceededMaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 2), + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); try std.testing.expectError( error.ExceededMaxDepth, - serializer.sliceMaxDepth(maybe_recurse, .{}, 2), + sz.sliceMaxDepth(maybe_recurse, .{}, 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - try serializer.sliceArbitraryDepth(maybe_recurse, .{}); + try sz.sliceArbitraryDepth(maybe_recurse, .{}); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); } @@ -1858,17 +1863,17 @@ test "std.zon depth limits" { var temp: [1]Recurse = .{.{ .r = &.{} }}; const maybe_recurse: []const Recurse = &temp; - try serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 3); + try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); - try serializer.sliceMaxDepth(maybe_recurse, .{}, 3); + try sz.sliceMaxDepth(maybe_recurse, .{}, 3); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); - try serializer.sliceArbitraryDepth(maybe_recurse, .{}); + try sz.sliceArbitraryDepth(maybe_recurse, .{}); try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); buf.clearRetainingCapacity(); } @@ -1881,15 +1886,15 @@ test "std.zon depth limits" { try std.testing.expectError( error.ExceededMaxDepth, - serializeMaxDepth(maybe_recurse, .{}, buf.writer().any(), 128), + serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 128), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); try std.testing.expectError( error.ExceededMaxDepth, - serializer.sliceMaxDepth(maybe_recurse, .{}, 128), + sz.sliceMaxDepth(maybe_recurse, .{}, 128), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1897,30 +1902,30 @@ test "std.zon depth limits" { // Max depth on other parts of the lower level API { - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); const maybe_recurse: []const Recurse = &.{}; - try std.testing.expectError(error.ExceededMaxDepth, serializer.valueMaxDepth(1, .{}, 0)); - try serializer.valueMaxDepth(2, .{}, 1); - try serializer.value(3, .{}); - try serializer.valueArbitraryDepth(maybe_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 serializer.startStruct(.{}); + 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 serializer.startTuple(.{}); + 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 serializer.startSlice(.{}); + var a = try sz.startSlice(.{}); try std.testing.expectError(error.ExceededMaxDepth, a.itemMaxDepth(1, .{}, 0)); try a.itemMaxDepth(8, .{}, 1); try a.item(9, .{}); @@ -2039,37 +2044,37 @@ test "std.zon stringify primitives" { test "std.zon stringify ident" { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); - var serializer = Serializer.init(buf.writer().any(), .{}); + var sz = serializer(buf.writer(), .{}); - try serializer.ident("a"); + try sz.ident("a"); try std.testing.expectEqualStrings("a", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("foo_1"); + try sz.ident("foo_1"); try std.testing.expectEqualStrings("foo_1", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("_foo_1"); + try sz.ident("_foo_1"); try std.testing.expectEqualStrings("_foo_1", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("foo bar"); + try sz.ident("foo bar"); try std.testing.expectEqualStrings("@\"foo bar\"", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("1foo"); + try sz.ident("1foo"); try std.testing.expectEqualStrings("@\"1foo\"", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("var"); + try sz.ident("var"); try std.testing.expectEqualStrings("@\"var\"", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("true"); + try sz.ident("true"); try std.testing.expectEqualStrings("true", buf.items); buf.clearRetainingCapacity(); - try serializer.ident("_"); + try sz.ident("_"); try std.testing.expectEqualStrings("_", buf.items); buf.clearRetainingCapacity(); From 977a714a173935670395c5f510d52e917146a799 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 9 Jan 2025 23:08:27 -0800 Subject: [PATCH 51/51] Stops using parse.zig as a struct --- lib/std/zon/parse.zig | 1527 ++++++++++++++++++++--------------------- 1 file changed, 755 insertions(+), 772 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 87ec7c31a8c3..6904b95c88cd 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -19,10 +19,8 @@ const NumberLiteralError = std.zig.number_literal.Error; const assert = std.debug.assert; const ArrayListUnmanaged = std.ArrayListUnmanaged; -gpa: Allocator, -ast: Ast, -zoir: Zoir, -status: ?*Status, +/// Rename when adding or removing support for a type. +const valid_types = {}; /// Configuration for the runtime parser. pub const Options = struct { @@ -237,61 +235,6 @@ pub const Status = struct { } }; -test "std.zon ast errors" { - // Test multiple 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}); -} - /// Parses the given slice as ZON. /// /// Returns `error.OutOfMemory` on allocation failure, or `error.ParseZon` error if the ZON is @@ -331,13 +274,6 @@ pub fn fromSlice( return fromZoir(T, gpa, ast, zoir, status, options); } -test "std.zon fromSlice syntax error" { - try std.testing.expectError( - error.ParseZon, - fromSlice(u8, std.testing.allocator, ".{", null, .{}), - ); -} - /// Like `fromSlice`, but operates on `Zoir` instead of ZON source. pub fn fromZoir( comptime T: type, @@ -370,7 +306,7 @@ pub fn fromZoirNode( return error.ParseZon; } - var parser = @This(){ + var parser: Parser = .{ .gpa = gpa, .ast = ast, .zoir = zoir, @@ -380,48 +316,6 @@ pub fn fromZoirNode( return parser.parseExpr(T, options, node); } -fn requiresAllocator(comptime T: type) bool { - _ = valid_types; - return switch (@typeInfo(T)) { - .pointer => true, - .array => |array| requiresAllocator(array.child), - .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { - if (requiresAllocator(field.type)) { - break true; - } - } else false, - .@"union" => |@"union"| inline for (@"union".fields) |field| { - if (requiresAllocator(field.type)) { - break true; - } - } else false, - .optional => |optional| requiresAllocator(optional.child), - else => false, - }; -} - -test "std.zon requiresAllocator" { - try std.testing.expect(!requiresAllocator(u8)); - try std.testing.expect(!requiresAllocator(f32)); - try std.testing.expect(!requiresAllocator(enum { foo })); - try std.testing.expect(!requiresAllocator(struct { f32 })); - try std.testing.expect(!requiresAllocator(struct { x: f32 })); - try std.testing.expect(!requiresAllocator([2]u8)); - try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); - try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); - try std.testing.expect(!requiresAllocator(?f32)); - try std.testing.expect(!requiresAllocator(void)); - try std.testing.expect(!requiresAllocator(@TypeOf(null))); - - try std.testing.expect(requiresAllocator([]u8)); - try std.testing.expect(requiresAllocator(*struct { u8, u8 })); - try std.testing.expect(requiresAllocator([1][]const u8)); - try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); - try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); - try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); - try std.testing.expect(requiresAllocator(?[]u8)); -} - /// Frees ZON values. /// /// Provided for convenience, you may also free these values on your own using the same allocator @@ -470,45 +364,763 @@ pub fn free(gpa: Allocator, value: anytype) void { } } -/// Rename when adding or removing support for a type. -const valid_types = {}; - -fn parseExpr( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { +fn requiresAllocator(comptime T: type) bool { _ = valid_types; + return switch (@typeInfo(T)) { + .pointer => true, + .array => |array| requiresAllocator(array.child), + .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .@"union" => |@"union"| inline for (@"union".fields) |field| { + if (requiresAllocator(field.type)) { + break true; + } + } else false, + .optional => |optional| requiresAllocator(optional.child), + else => false, + }; +} + +const Parser = struct { + gpa: Allocator, + ast: Ast, + zoir: Zoir, + status: ?*Status, + + fn parseExpr( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + _ = valid_types; + switch (@typeInfo(T)) { + .bool => return self.parseBool(node), + .int => return self.parseInt(T, node), + .float => return self.parseFloat(T, node), + .@"enum" => return self.parseEnumLiteral(T, node), + .pointer => return self.parsePointer(T, options, node), + .array => return self.parseArray(T, options, node), + .@"struct" => |@"struct"| if (@"struct".is_tuple) + return self.parseTuple(T, options, node) + else + return self.parseStruct(T, options, node), + .@"union" => return self.parseUnion(T, options, node), + .optional => return self.parseOptional(T, options, node), + + else => @compileError("type '" ++ @typeName(T) ++ "' is not available in ZON"), + } + } + + fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { + switch (node.get(self.zoir)) { + .true => return true, + .false => return false, + else => return self.failNode(node, "expected type 'bool'"), + } + } + + fn parseInt( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .int_literal => |int| switch (int) { + .small => |val| return std.math.cast(T, val) orelse + self.failCannotRepresent(T, node), + .big => |val| return val.toInt(T) catch + self.failCannotRepresent(T, node), + }, + .float_literal => |val| return intFromFloatExact(T, val) orelse + self.failCannotRepresent(T, node), + + .char_literal => |val| return std.math.cast(T, val) orelse + self.failCannotRepresent(T, node), + else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), + } + } + + fn parseFloat( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .int_literal => |int| switch (int) { + .small => |val| return @floatFromInt(val), + .big => |val| return val.toFloat(T), + }, + .float_literal => |val| return @floatCast(val), + .pos_inf => return std.math.inf(T), + .neg_inf => return -std.math.inf(T), + .nan => return std.math.nan(T), + .char_literal => |val| return @floatFromInt(val), + else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), + } + } + + fn parseEnumLiteral( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .enum_literal => |string| { + // Create a comptime string map for the enum fields + const enum_fields = @typeInfo(T).@"enum".fields; + comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; + inline for (enum_fields, 0..) |field, i| { + kvs_list[i] = .{ field.name, @enumFromInt(field.value) }; + } + const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); + + // Get the tag if it exists + return enum_tags.get(string.get(self.zoir)) orelse + self.failUnexpectedField(T, node, null); + }, + else => return self.failNode(node, "expected enum literal"), + } + } + + fn parsePointer( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + switch (node.get(self.zoir)) { + .string_literal => return try self.parseString(T, node), + .array_literal => |nodes| return try self.parseSlice(T, options, nodes), + .empty_literal => return try self.parseSlice(T, options, .{ .start = node, .len = 0 }), + else => return self.failExpectedContainer(T, node), + } + } + + fn parseString( + self: *@This(), + comptime T: type, + node: Zoir.Node.Index, + ) !T { + const ast_node = node.getAstNode(self.zoir); + const pointer = @typeInfo(T).pointer; + var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); + if (pointer.sentinel != 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 @as(*const u8, @ptrCast(pointer.sentinel)).* != 0) or + pointer.alignment != 1) + { + return self.failExpectedContainer(T, node); + } + + if (pointer.sentinel != null) { + return try buf.toOwnedSliceSentinel(self.gpa, 0); + } else { + return try buf.toOwnedSlice(self.gpa); + } + } + + fn parseSlice( + self: *@This(), + comptime T: type, + comptime options: Options, + nodes: Zoir.Node.Index.Range, + ) !T { + const pointer = @typeInfo(T).pointer; + + // Make sure we're working with a slice + switch (pointer.size) { + .Slice => {}, + .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), + } + + // Allocate the slice + const sentinel = if (pointer.sentinel) |s| @as(*const pointer.child, @ptrCast(s)).* else null; + const slice = try self.gpa.allocWithOptions( + pointer.child, + nodes.len, + pointer.alignment, + sentinel, + ); + errdefer self.gpa.free(slice); + + // Parse the elements and return the slice + for (0..nodes.len) |i| { + errdefer if (options.free_on_error) { + for (slice[0..i]) |item| { + free(self.gpa, item); + } + }; + slice[i] = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); + } + + return slice; + } + + fn parseArray( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.failExpectedContainer(T, node), + }; + + const array_info = @typeInfo(T).array; + + // Check if the size matches + if (nodes.len > array_info.len) { + return self.failNodeFmt( + nodes.at(array_info.len), + "index {} outside of tuple length {}", + .{ array_info.len, array_info.len }, + ); + } else if (nodes.len < array_info.len) { + switch (nodes.len) { + inline 0...array_info.len => |n| { + return self.failNodeFmt(node, "missing tuple field with index {}", .{n}); + }, + else => unreachable, + } + } + + // Parse the elements and return the array + var result: T = undefined; + for (0..result.len) |i| { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + for (result[0..i]) |item| { + free(self.gpa, item); + } + }; + + result[i] = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); + } + return result; + } + + fn parseStruct( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + const repr = node.get(self.zoir); + const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (repr) { + .struct_literal => |nodes| nodes, + .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, + else => return self.failExpectedContainer(T, node), + }; + + const field_infos = @typeInfo(T).@"struct".fields; + + // Gather info on the fields + const field_indices = b: { + comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + inline for (field_infos, 0..) |field, i| { + kvs_list[i] = .{ field.name, i }; + } + break :b std.StaticStringMap(usize).initComptime(kvs_list); + }; + + // Parse the struct + var result: T = undefined; + var field_found: [field_infos.len]bool = .{false} ** field_infos.len; + + // If we fail partway through, free all already initialized fields + var initialized: usize = 0; + errdefer if (options.free_on_error and field_infos.len > 0) { + for (fields.names[0..initialized]) |name_runtime| { + switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { + inline 0...(field_infos.len - 1) => |name_index| { + const name = field_infos[name_index].name; + free(self.gpa, @field(result, name)); + }, + else => unreachable, // Can't be out of bounds + } + } + }; + + // Fill in the fields we found + for (0..fields.names.len) |i| { + const field_index = b: { + const name = fields.names[i].get(self.zoir); + break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { + continue; + } else { + return self.failUnexpectedField(T, node, i); + }; + }; + + // We now know the array is not zero sized (assert this so the code compiles) + if (field_found.len == 0) unreachable; + + if (field_found[field_index]) { + var buf: [2]Ast.Node.Index = undefined; + const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; + const field_node = struct_init.ast.fields[i]; + const token = self.ast.firstToken(field_node) - 2; + return self.failTokenFmt(token, 0, "duplicate struct field name", .{}); + } + field_found[field_index] = true; + + switch (field_index) { + inline 0...(field_infos.len - 1) => |j| { + if (field_infos[j].is_comptime) { + return self.failRuntimeValueComptimeVar(node, j); + } else { + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + options, + fields.vals.at(@intCast(i)), + ); + } + }, + else => unreachable, // Can't be out of bounds + } + + initialized += 1; + } + + // Fill in any missing default fields + inline for (field_found, 0..) |found, i| { + if (!found) { + const field_info = field_infos[i]; + if (field_info.default_value) |default| { + const typed: *const field_info.type = @ptrCast(@alignCast(default)); + @field(result, field_info.name) = typed.*; + } else { + return self.failNodeFmt( + node, + "missing required field {s}", + .{field_infos[i].name}, + ); + } + } + } + + return result; + } + + fn parseTuple( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.failExpectedContainer(T, node), + }; + + var result: T = undefined; + const field_infos = @typeInfo(T).@"struct".fields; + + if (nodes.len > field_infos.len) { + return self.failNodeFmt( + nodes.at(field_infos.len), + "index {} outside of tuple length {}", + .{ field_infos.len, field_infos.len }, + ); + } + + inline for (0..field_infos.len) |i| { + // Check if we're out of bounds + if (i >= nodes.len) { + if (field_infos[i].default_value) |default| { + const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); + @field(result, field_infos[i].name) = typed.*; + } else { + return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); + } + } else { + // If we fail to parse this field, free all fields before it + errdefer if (options.free_on_error) { + inline for (0..i) |j| { + if (j >= i) break; + free(self.gpa, result[j]); + } + }; + + if (field_infos[i].is_comptime) { + return self.failRuntimeValueComptimeVar(node, i); + } else { + result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); + } + } + } + + return result; + } + + fn parseUnion( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + const @"union" = @typeInfo(T).@"union"; + const field_infos = @"union".fields; + + if (field_infos.len == 0) { + @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); + } + + // Gather info on the fields + const field_indices = b: { + comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + inline for (field_infos, 0..) |field, i| { + kvs_list[i] = .{ field.name, i }; + } + break :b std.StaticStringMap(usize).initComptime(kvs_list); + }; + + // Parse the union + switch (node.get(self.zoir)) { + .enum_literal => |string| { + // The union must be tagged for an enum literal to coerce to it + if (@"union".tag_type == null) { + return self.failNode(node, "expected union"); + } + + // Get the index of the named field. We don't use `parseEnum` here as + // the order of the enum and the order of the union might not match! + const field_index = b: { + break :b field_indices.get(string.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, null); + }; + + // Initialize the union from the given field. + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + // Fail if the field is not void + if (field_infos[i].type != void) + return self.failNode(node, "expected union"); + + // Instantiate the union + return @unionInit(T, field_infos[i].name, {}); + }, + else => unreachable, // Can't be out of bounds + } + }, + .struct_literal => |struct_fields| { + if (struct_fields.names.len != 1) { + return self.failNode(node, "expected union"); + } + + // Fill in the field we found + const field_name = struct_fields.names[0]; + const field_val = struct_fields.vals.at(0); + const field_index = field_indices.get(field_name.get(self.zoir)) orelse + return self.failUnexpectedField(T, node, 0); + + switch (field_index) { + inline 0...field_infos.len - 1 => |i| { + if (field_infos[i].type == void) { + return self.failNode(field_val, "expected type 'void'"); + } else { + const value = try self.parseExpr(field_infos[i].type, options, field_val); + return @unionInit(T, field_infos[i].name, value); + } + }, + else => unreachable, // Can't be out of bounds + } + }, + else => return self.failNode(node, "expected union"), + } + } + + fn parseOptional( + self: *@This(), + comptime T: type, + comptime options: Options, + node: Zoir.Node.Index, + ) !T { + if (node.get(self.zoir) == .null) { + return null; + } + + return try self.parseExpr(@typeInfo(T).optional.child, options, node); + } + + fn failTokenFmt( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + if (self.status) |s| s.type_check = .{ + .token = token, + .offset = offset, + .message = try std.fmt.allocPrint(self.gpa, fmt, args), + .owned = true, + }; + return error.ParseZon; + } + + fn failNodeFmt( + self: @This(), + node: Zoir.Node.Index, + comptime fmt: []const u8, + args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failTokenFmt(token, 0, fmt, args); + } + + fn failToken( + self: @This(), + failure: Error.TypeCheckFailure, + ) error{ParseZon} { + @branchHint(.cold); + if (self.status) |s| s.type_check = failure; + return error.ParseZon; + } + + fn failNode( + self: @This(), + node: Zoir.Node.Index, + message: []const u8, + ) error{ParseZon} { + @branchHint(.cold); + const main_tokens = self.ast.nodes.items(.main_token); + const token = main_tokens[node.getAstNode(self.zoir)]; + return self.failToken(.{ + .token = token, + .offset = 0, + .message = message, + .owned = false, + }); + } + + fn failCannotRepresent( + self: @This(), + comptime T: type, + node: Zoir.Node.Index, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); + } + + fn failUnexpectedField( + self: @This(), + T: type, + node: Zoir.Node.Index, + field: ?usize, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const token = if (field) |f| b: { + var buf: [2]Ast.Node.Index = undefined; + const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; + const field_node = struct_init.ast.fields[f]; + break :b self.ast.firstToken(field_node) - 2; + } else b: { + const main_tokens = self.ast.nodes.items(.main_token); + break :b main_tokens[node.getAstNode(self.zoir)]; + }; + switch (@typeInfo(T)) { + inline .@"struct", .@"union", .@"enum" => |info| { + if (info.fields.len == 0) { + return self.failTokenFmt(token, 0, "unexpected field, no fields expected", .{}); + } else { + const msg = "unexpected field, supported fields: "; + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, msg.len * 2); + defer buf.deinit(self.gpa); + const writer = buf.writer(self.gpa); + try writer.writeAll(msg); + inline for (info.fields, 0..) |field_info, i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("{}", .{std.zig.fmtId(field_info.name)}); + } + return self.failToken(.{ + .token = token, + .offset = 0, + .message = try buf.toOwnedSlice(self.gpa), + .owned = true, + }); + } + }, + else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + } + } + + fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + switch (@typeInfo(T)) { + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + return self.failNode(node, "expected tuple"); + } else { + return self.failNode(node, "expected struct"); + }, + .@"union" => return self.failNode(node, "expected union"), + .array => return self.failNode(node, "expected tuple"), + .pointer => |pointer| { + if (pointer.child == u8 and + pointer.size == .Slice and + pointer.is_const and + (pointer.sentinel == null or @as(*const u8, @ptrCast(pointer.sentinel)).* == 0) and + pointer.alignment == 1) + { + return self.failNode(node, "expected string"); + } else { + return self.failNode(node, "expected tuple"); + } + }, + else => {}, + } + @compileError("unreachable, should not be called for type " ++ @typeName(T)); + } + + // Technically we could do this if we were willing to do a deep equal to verify + // the value matched, but doing so doesn't seem to support any real use cases + // so isn't worth the complexity at the moment. + fn failRuntimeValueComptimeVar( + self: @This(), + node: Zoir.Node.Index, + field: usize, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + const ast_node = node.getAstNode(self.zoir); + var buf: [2]Ast.Node.Index = undefined; + const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: { + const field_node = struct_init.ast.fields[field]; + break :b self.ast.firstToken(field_node); + } else b: { + const array_init = self.ast.fullArrayInit(&buf, ast_node).?; + const value_node = array_init.ast.elements[field]; + break :b self.ast.firstToken(value_node); + }; + return self.failTokenFmt(token, 0, "cannot store runtime value in compile time variable", .{}); + } +}; + +fn intFromFloatExact(comptime T: type, value: anytype) ?T { + switch (@typeInfo(@TypeOf(value))) { + .float => {}, + else => @compileError(@typeName(@TypeOf(value)) ++ " is not a runtime floating point type"), + } switch (@typeInfo(T)) { - .bool => return self.parseBool(node), - .int => return self.parseInt(T, node), - .float => return self.parseFloat(T, node), - .@"enum" => return self.parseEnumLiteral(T, node), - .pointer => return self.parsePointer(T, options, node), - .array => return self.parseArray(T, options, node), - .@"struct" => |@"struct"| if (@"struct".is_tuple) - return self.parseTuple(T, options, node) - else - return self.parseStruct(T, options, node), - .@"union" => return self.parseUnion(T, options, node), - .optional => return self.parseOptional(T, options, node), - - else => @compileError("type '" ++ @typeName(T) ++ "' is not available in ZON"), + .int => {}, + else => @compileError(@typeName(T) ++ " is not a runtime integer type"), + } + + if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { + return null; } + + if (std.math.isNan(value) or std.math.trunc(value) != value) { + return null; + } + + return @as(T, @intFromFloat(value)); +} + +test "std.zon requiresAllocator" { + try std.testing.expect(!requiresAllocator(u8)); + try std.testing.expect(!requiresAllocator(f32)); + try std.testing.expect(!requiresAllocator(enum { foo })); + try std.testing.expect(!requiresAllocator(struct { f32 })); + try std.testing.expect(!requiresAllocator(struct { x: f32 })); + try std.testing.expect(!requiresAllocator([2]u8)); + try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); + try std.testing.expect(!requiresAllocator(?f32)); + try std.testing.expect(!requiresAllocator(void)); + try std.testing.expect(!requiresAllocator(@TypeOf(null))); + + try std.testing.expect(requiresAllocator([]u8)); + try std.testing.expect(requiresAllocator(*struct { u8, u8 })); + try std.testing.expect(requiresAllocator([1][]const u8)); + try std.testing.expect(requiresAllocator(struct { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); + try std.testing.expect(requiresAllocator(?[]u8)); +} + +test "std.zon ast errors" { + const gpa = std.testing.allocator; + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(struct {}, gpa, ".{.x = 1 .y = 2}", &status, .{}), + ); + try std.testing.expectFmt("1:13: error: expected ',' after initializer\n", "{}", .{status}); +} + +test "std.zon comments" { + const gpa = std.testing.allocator; + + try std.testing.expectEqual(@as(u8, 10), fromSlice(u8, gpa, + \\// comment + \\10 // comment + \\// comment + , null, .{})); + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.ParseZon, fromSlice(u8, gpa, + \\//! comment + \\10 // comment + \\// comment + , &status, .{})); + try std.testing.expectFmt( + "1:1: error: expected expression, found 'a document comment'\n", + "{}", + .{status}, + ); + } +} + +test "std.zon failure/oom formatting" { + const gpa = std.testing.allocator; + var failing_allocator = std.testing.FailingAllocator.init(gpa, .{ + .fail_index = 0, + .resize_fail_index = 0, + }); + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError(error.OutOfMemory, fromSlice( + []const u8, + failing_allocator.allocator(), + "\"foo\"", + &status, + .{}, + )); + try std.testing.expectFmt("", "{}", .{status}); } -fn parseOptional( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - if (node.get(self.zoir) == .null) { - return null; - } - - return try self.parseExpr(@typeInfo(T).optional.child, options, node); +test "std.zon fromSlice syntax error" { + try std.testing.expectError( + error.ParseZon, + fromSlice(u8, std.testing.allocator, ".{", null, .{}), + ); } test "std.zon optional" { @@ -532,83 +1144,6 @@ test "std.zon optional" { } } -fn parseUnion( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - const @"union" = @typeInfo(T).@"union"; - const field_infos = @"union".fields; - - if (field_infos.len == 0) { - @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); - } - - // Gather info on the fields - const field_indices = b: { - comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; - inline for (field_infos, 0..) |field, i| { - kvs_list[i] = .{ field.name, i }; - } - break :b std.StaticStringMap(usize).initComptime(kvs_list); - }; - - // Parse the union - switch (node.get(self.zoir)) { - .enum_literal => |string| { - // The union must be tagged for an enum literal to coerce to it - if (@"union".tag_type == null) { - return self.failNode(node, "expected union"); - } - - // Get the index of the named field. We don't use `parseEnum` here as - // the order of the enum and the order of the union might not match! - const field_index = b: { - break :b field_indices.get(string.get(self.zoir)) orelse - return self.failUnexpectedField(T, node, null); - }; - - // Initialize the union from the given field. - switch (field_index) { - inline 0...field_infos.len - 1 => |i| { - // Fail if the field is not void - if (field_infos[i].type != void) - return self.failNode(node, "expected union"); - - // Instantiate the union - return @unionInit(T, field_infos[i].name, {}); - }, - else => unreachable, // Can't be out of bounds - } - }, - .struct_literal => |struct_fields| { - if (struct_fields.names.len != 1) { - return self.failNode(node, "expected union"); - } - - // Fill in the field we found - const field_name = struct_fields.names[0]; - const field_val = struct_fields.vals.at(0); - const field_index = field_indices.get(field_name.get(self.zoir)) orelse - return self.failUnexpectedField(T, node, 0); - - switch (field_index) { - inline 0...field_infos.len - 1 => |i| { - if (field_infos[i].type == void) { - return self.failNode(field_val, "expected type 'void'"); - } else { - const value = try self.parseExpr(field_infos[i].type, options, field_val); - return @unionInit(T, field_infos[i].name, value); - } - }, - else => unreachable, // Can't be out of bounds - } - }, - else => return self.failNode(node, "expected union"), - } -} - test "std.zon unions" { const gpa = std.testing.allocator; @@ -728,101 +1263,6 @@ test "std.zon unions" { } } -fn parseStruct( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - const repr = node.get(self.zoir); - const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (repr) { - .struct_literal => |nodes| nodes, - .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, - else => return self.failExpectedContainer(T, node), - }; - - const field_infos = @typeInfo(T).@"struct".fields; - - // Gather info on the fields - const field_indices = b: { - comptime var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; - inline for (field_infos, 0..) |field, i| { - kvs_list[i] = .{ field.name, i }; - } - break :b std.StaticStringMap(usize).initComptime(kvs_list); - }; - - // Parse the struct - var result: T = undefined; - var field_found: [field_infos.len]bool = .{false} ** field_infos.len; - - // If we fail partway through, free all already initialized fields - var initialized: usize = 0; - errdefer if (options.free_on_error and field_infos.len > 0) { - for (fields.names[0..initialized]) |name_runtime| { - switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { - inline 0...(field_infos.len - 1) => |name_index| { - const name = field_infos[name_index].name; - free(self.gpa, @field(result, name)); - }, - else => unreachable, // Can't be out of bounds - } - } - }; - - // Fill in the fields we found - for (0..fields.names.len) |i| { - const field_index = b: { - const name = fields.names[i].get(self.zoir); - break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { - continue; - } else { - return self.failUnexpectedField(T, node, i); - }; - }; - - // We now know the array is not zero sized (assert this so the code compiles) - if (field_found.len == 0) unreachable; - - if (field_found[field_index]) { - return self.failDuplicateField(node, i); - } - field_found[field_index] = true; - - switch (field_index) { - inline 0...(field_infos.len - 1) => |j| { - if (field_infos[j].is_comptime) { - return self.failRuntimeValueComptimeVar(node, j); - } else { - @field(result, field_infos[j].name) = try self.parseExpr( - field_infos[j].type, - options, - fields.vals.at(@intCast(i)), - ); - } - }, - else => unreachable, // Can't be out of bounds - } - - initialized += 1; - } - - // Fill in any missing default fields - inline for (field_found, 0..) |found, i| { - if (!found) { - const field_info = field_infos[i]; - if (field_info.default_value) |default| { - const typed: *const field_info.type = @ptrCast(@alignCast(default)); - @field(result, field_info.name) = typed.*; - } else { - return self.failMissingField(field_infos[i].name, node); - } - } - } - - return result; -} - test "std.zon structs" { const gpa = std.testing.allocator; @@ -1039,58 +1479,6 @@ test "std.zon structs" { } } -fn parseTuple( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { - .array_literal => |nodes| nodes, - .empty_literal => .{ .start = node, .len = 0 }, - else => return self.failExpectedContainer(T, node), - }; - - var result: T = undefined; - const field_infos = @typeInfo(T).@"struct".fields; - - if (nodes.len > field_infos.len) { - return self.failNodeFmt( - nodes.at(field_infos.len), - "index {} outside of tuple length {}", - .{ field_infos.len, field_infos.len }, - ); - } - - inline for (0..field_infos.len) |i| { - // Check if we're out of bounds - if (i >= nodes.len) { - if (field_infos[i].default_value) |default| { - const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); - @field(result, field_infos[i].name) = typed.*; - } else { - return self.failNodeFmt(node, "missing tuple field with index {}", .{i}); - } - } else { - // If we fail to parse this field, free all fields before it - errdefer if (options.free_on_error) { - inline for (0..i) |j| { - if (j >= i) break; - free(self.gpa, result[j]); - } - }; - - if (field_infos[i].is_comptime) { - return self.failRuntimeValueComptimeVar(node, i); - } else { - result[i] = try self.parseExpr(field_infos[i].type, options, nodes.at(i)); - } - } - } - - return result; -} - test "std.zon tuples" { const gpa = std.testing.allocator; @@ -1195,51 +1583,6 @@ test "std.zon tuples" { } } -fn parseArray( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { - .array_literal => |nodes| nodes, - .empty_literal => .{ .start = node, .len = 0 }, - else => return self.failExpectedContainer(T, node), - }; - - const array_info = @typeInfo(T).array; - - // Check if the size matches - if (nodes.len > array_info.len) { - return self.failNodeFmt( - nodes.at(array_info.len), - "index {} outside of tuple length {}", - .{ array_info.len, array_info.len }, - ); - } else if (nodes.len < array_info.len) { - switch (nodes.len) { - inline 0...array_info.len => |n| { - return self.failNodeFmt(node, "missing tuple field with index {}", .{n}); - }, - else => unreachable, - } - } - - // Parse the elements and return the array - var result: T = undefined; - for (0..result.len) |i| { - // If we fail to parse this field, free all fields before it - errdefer if (options.free_on_error) { - for (result[0..i]) |item| { - free(self.gpa, item); - } - }; - - result[i] = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); - } - return result; -} - // Test sizes 0 to 3 since small sizes get parsed differently test "std.zon arrays and slices" { // Issue: https://github.com/ziglang/zig/issues/20881 @@ -1457,94 +1800,6 @@ test "std.zon arrays and slices" { } } -fn parsePointer( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, -) !T { - switch (node.get(self.zoir)) { - .string_literal => return try self.parseString(T, node), - .array_literal => |nodes| return try self.parseSlice(T, options, nodes), - .empty_literal => return try self.parseSlice(T, options, .{ .start = node, .len = 0 }), - else => return self.failExpectedContainer(T, node), - } -} - -fn parseString( - self: *@This(), - comptime T: type, - node: Zoir.Node.Index, -) !T { - const ast_node = node.getAstNode(self.zoir); - const pointer = @typeInfo(T).pointer; - var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); - if (pointer.sentinel != 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 @as(*const u8, @ptrCast(pointer.sentinel)).* != 0) or - pointer.alignment != 1) - { - return self.failExpectedContainer(T, node); - } - - if (pointer.sentinel != null) { - return try buf.toOwnedSliceSentinel(self.gpa, 0); - } else { - return try buf.toOwnedSlice(self.gpa); - } -} - -fn parseSlice( - self: *@This(), - comptime T: type, - comptime options: Options, - nodes: Zoir.Node.Index.Range, -) !T { - const pointer = @typeInfo(T).pointer; - - // Make sure we're working with a slice - switch (pointer.size) { - .Slice => {}, - .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), - } - - // Allocate the slice - const sentinel = if (pointer.sentinel) |s| @as(*const pointer.child, @ptrCast(s)).* else null; - const slice = try self.gpa.allocWithOptions( - pointer.child, - nodes.len, - pointer.alignment, - sentinel, - ); - errdefer self.gpa.free(slice); - - // Parse the elements and return the slice - for (0..nodes.len) |i| { - errdefer if (options.free_on_error) { - for (slice[0..i]) |item| { - free(self.gpa, item); - } - }; - slice[i] = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); - } - - return slice; -} - test "std.zon string literal" { const gpa = std.testing.allocator; @@ -1788,29 +2043,6 @@ test "std.zon string literal" { } } -fn parseEnumLiteral( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, -) !T { - switch (node.get(self.zoir)) { - .enum_literal => |string| { - // Create a comptime string map for the enum fields - const enum_fields = @typeInfo(T).@"enum".fields; - comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; - inline for (enum_fields, 0..) |field, i| { - kvs_list[i] = .{ field.name, @enumFromInt(field.value) }; - } - const enum_tags = std.StaticStringMap(T).initComptime(kvs_list); - - // Get the tag if it exists - return enum_tags.get(string.get(self.zoir)) orelse - self.failUnexpectedField(T, node, null); - }, - else => return self.failNode(node, "expected enum literal"), - } -} - test "std.zon enum literals" { const gpa = std.testing.allocator; @@ -1887,194 +2119,6 @@ test "std.zon enum literals" { } } -fn failTokenFmt( - self: @This(), - token: Ast.TokenIndex, - offset: u32, - comptime fmt: []const u8, - args: anytype, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - if (self.status) |s| s.type_check = .{ - .token = token, - .offset = offset, - .message = try std.fmt.allocPrint(self.gpa, fmt, args), - .owned = true, - }; - return error.ParseZon; -} - -fn failNodeFmt( - self: @This(), - node: Zoir.Node.Index, - comptime fmt: []const u8, - args: anytype, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node.getAstNode(self.zoir)]; - return self.failTokenFmt(token, 0, fmt, args); -} - -fn failToken( - self: @This(), - failure: Error.TypeCheckFailure, -) error{ParseZon} { - @branchHint(.cold); - if (self.status) |s| s.type_check = failure; - return error.ParseZon; -} - -fn failNode( - self: @This(), - node: Zoir.Node.Index, - message: []const u8, -) error{ParseZon} { - @branchHint(.cold); - const main_tokens = self.ast.nodes.items(.main_token); - const token = main_tokens[node.getAstNode(self.zoir)]; - return self.failToken(.{ - .token = token, - .offset = 0, - .message = message, - .owned = false, - }); -} - -fn failCannotRepresent( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); -} - -fn failUnexpectedField( - self: @This(), - T: type, - node: Zoir.Node.Index, - field: ?usize, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - const token = if (field) |f| b: { - var buf: [2]Ast.Node.Index = undefined; - const struct_init = self.ast.fullStructInit(&buf, node.getAstNode(self.zoir)).?; - const field_node = struct_init.ast.fields[f]; - break :b self.ast.firstToken(field_node) - 2; - } else b: { - const main_tokens = self.ast.nodes.items(.main_token); - break :b main_tokens[node.getAstNode(self.zoir)]; - }; - switch (@typeInfo(T)) { - inline .@"struct", .@"union", .@"enum" => |info| { - if (info.fields.len == 0) { - return self.failTokenFmt(token, 0, "unexpected field, no fields expected", .{}); - } else { - const msg = "unexpected field, supported fields: "; - var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, msg.len * 2); - defer buf.deinit(self.gpa); - const writer = buf.writer(self.gpa); - try writer.writeAll(msg); - inline for (info.fields, 0..) |field_info, i| { - if (i != 0) try writer.writeAll(", "); - try writer.print("{}", .{std.zig.fmtId(field_info.name)}); - } - return self.failToken(.{ - .token = token, - .offset = 0, - .message = try buf.toOwnedSlice(self.gpa), - .owned = true, - }); - } - }, - else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), - } -} - -fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - switch (@typeInfo(T)) { - .@"struct" => |@"struct"| if (@"struct".is_tuple) { - return self.failNode(node, "expected tuple"); - } else { - return self.failNode(node, "expected struct"); - }, - .@"union" => return self.failNode(node, "expected union"), - .array => return self.failNode(node, "expected tuple"), - .pointer => |pointer| { - if (pointer.child == u8 and - pointer.size == .Slice and - pointer.is_const and - (pointer.sentinel == null or @as(*const u8, @ptrCast(pointer.sentinel)).* == 0) and - pointer.alignment == 1) - { - return self.failNode(node, "expected string"); - } else { - return self.failNode(node, "expected tuple"); - } - }, - else => {}, - } - @compileError("unreachable, should not be called for type " ++ @typeName(T)); -} - -fn failMissingField( - self: @This(), - comptime name: []const u8, - node: Zoir.Node.Index, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - return self.failNodeFmt( - node, - "missing required field {s}", - .{name}, - ); -} - -fn failDuplicateField( - self: @This(), - node: Zoir.Node.Index, - field: usize, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - 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[field]; - const token = self.ast.firstToken(field_node) - 2; - return self.failTokenFmt(token, 0, "duplicate struct field name", .{}); -} - -// Technically we could do this if we were willing to do a deep equal to verify -// the value matched, but doing so doesn't seem to support any real use cases -// so isn't worth the complexity at the moment. -fn failRuntimeValueComptimeVar( - self: @This(), - node: Zoir.Node.Index, - field: usize, -) error{ OutOfMemory, ParseZon } { - @branchHint(.cold); - const ast_node = node.getAstNode(self.zoir); - var buf: [2]Ast.Node.Index = undefined; - const token = if (self.ast.fullStructInit(&buf, ast_node)) |struct_init| b: { - const field_node = struct_init.ast.fields[field]; - break :b self.ast.firstToken(field_node); - } else b: { - const array_init = self.ast.fullArrayInit(&buf, ast_node).?; - const value_node = array_init.ast.elements[field]; - break :b self.ast.firstToken(value_node); - }; - return self.failTokenFmt(token, 0, "cannot store runtime value in compile time variable", .{}); -} - -fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { - switch (node.get(self.zoir)) { - .true => return true, - .false => return false, - else => return self.failNode(node, "expected type 'bool'"), - } -} - test "std.zon parse bool" { const gpa = std.testing.allocator; @@ -2105,67 +2149,6 @@ test "std.zon parse bool" { } } -fn parseInt( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, -) !T { - switch (node.get(self.zoir)) { - .int_literal => |int| switch (int) { - .small => |val| return std.math.cast(T, val) orelse - self.failCannotRepresent(T, node), - .big => |val| return val.toInt(T) catch - self.failCannotRepresent(T, node), - }, - .float_literal => |val| return intFromFloatExact(T, val) orelse - self.failCannotRepresent(T, node), - - .char_literal => |val| return std.math.cast(T, val) orelse - self.failCannotRepresent(T, node), - else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), - } -} - -fn parseFloat( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, -) !T { - switch (node.get(self.zoir)) { - .int_literal => |int| switch (int) { - .small => |val| return @floatFromInt(val), - .big => |val| return val.toFloat(T), - }, - .float_literal => |val| return @floatCast(val), - .pos_inf => return std.math.inf(T), - .neg_inf => return -std.math.inf(T), - .nan => return std.math.nan(T), - .char_literal => |val| return @floatFromInt(val), - else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), - } -} - -fn intFromFloatExact(comptime T: type, value: anytype) ?T { - switch (@typeInfo(@TypeOf(value))) { - .float => {}, - else => @compileError(@typeName(@TypeOf(value)) ++ " is not a runtime floating point type"), - } - switch (@typeInfo(T)) { - .int => {}, - else => @compileError(@typeName(T) ++ " is not a runtime integer type"), - } - - if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { - return null; - } - - if (std.math.isNan(value) or std.math.trunc(value) != value) { - return null; - } - - return @as(T, @intFromFloat(value)); -} - test "std.zon intFromFloatExact" { // Valid conversions try std.testing.expectEqual(@as(u8, 10), intFromFloatExact(u8, @as(f32, 10.0)).?);