From 11b56ada3f85c2d200321afdb3736fb5df9fc020 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 14:03:36 -0800 Subject: [PATCH 01/98] 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 1ecc7c7dcb52..1e9d291710c1 100644 --- a/build.zig +++ b/build.zig @@ -406,7 +406,7 @@ pub fn build(b: *std.Build) !void { const optimization_modes = chosen_opt_modes_buf[0..chosen_mode_index]; const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" }; - const fmt_exclude_paths = &.{"test/cases"}; + const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" }; const do_fmt = b.addFmt(.{ .paths = fmt_include_paths, .exclude_paths = fmt_exclude_paths, diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index c8f5103626c4..cf4691a6675e 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1584,7 +1584,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 3f69ce5aeb6b..cfd467f9b1f3 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 3ffddd1c4c7e..c7c10defb7cc 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8924,36 +8924,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( @@ -9448,7 +9434,18 @@ fn builtinCall( } else if (str.len == 0) { return astgen.failTok(str_lit_token, "import path cannot be empty", .{}); } - const result = try gz.addStrTok(.import, str.index, str_lit_token); + const res_ty = try ri.rl.resultType(gz, node) orelse .none; + const payload_index = try addExtra(gz.astgen, Zir.Inst.Import{ + .res_ty = res_ty, + .path = str.index, + }); + const result = try gz.add(.{ + .tag = .import, + .data = .{ .pl_tok = .{ + .src_tok = gz.tokenIndexToRelative(str_lit_token), + .payload_index = payload_index, + } }, + }); const gop = try astgen.imports.getOrPut(astgen.gpa, str.index); if (!gop.found_existing) { gop.value_ptr.* = str_lit_token; @@ -11551,9 +11548,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( @@ -11671,7 +11679,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; @@ -11800,32 +11808,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 8a19aaa3cbc9..3896e23778a5 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -1673,7 +1673,7 @@ pub const Inst = struct { .func = .pl_node, .func_inferred = .pl_node, .func_fancy = .pl_node, - .import = .str_tok, + .import = .pl_tok, .int = .int, .int_big = .str, .float = .float, @@ -3841,6 +3841,13 @@ pub const Inst = struct { /// If `.none`, restore unconditionally. operand: Ref, }; + + pub const Import = struct { + /// The result type of the import, or `.none` if none was available. + res_ty: Ref, + /// The import path. + path: NullTerminatedString, + }; }; pub const SpecialProng = enum { none, @"else", under }; diff --git a/lib/std/zig/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 22727adbd925..988a071b45ab 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1065,6 +1065,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 1362b16cc992..32df4a9e8167 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2220,7 +2220,10 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count()); for (zcu.import_table.values()) |file_index| { if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue; - comp.astgen_work_queue.writeItemAssumeCapacity(file_index); + const file = zcu.fileByIndex(file_index); + if (file.mode == .zig) { + comp.astgen_work_queue.writeItemAssumeCapacity(file_index); + } } if (comp.file_system_inputs) |fsi| { for (zcu.import_table.values()) |file_index| { @@ -4272,6 +4275,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(); @@ -4325,7 +4329,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 5b3a487a4939..29e5dad7c528 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -492,6 +492,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 30db9e90009b..1100cba77235 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -187,6 +187,7 @@ const Alignment = InternPool.Alignment; const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; const Cache = std.Build.Cache; +const zon = @import("zon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -13964,9 +13965,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 => { @@ -13983,12 +13987,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 74653bce01ae..ada402291409 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -685,6 +685,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, @@ -704,6 +707,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); @@ -778,7 +792,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; } @@ -895,6 +909,7 @@ pub const File = struct { pub const Index = InternPool.FileIndex; }; +/// Represents the contents of a file loaded with `@embedFile`. pub const EmbedFile = struct { /// Module that this file is a part of, managed externally. owner: *Package.Module, @@ -2372,6 +2387,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 2c8dc5480902..42ce8635b444 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1867,6 +1867,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const file = zcu.fileByIndex(file_index); + assert(file.mode == .zig); assert(zcu.fileRootType(file_index) == .none); if (file.status != .success_zir) { @@ -1992,6 +1993,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 }); @@ -2022,7 +2024,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; @@ -2103,6 +2107,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 205ce46d0aa9..f79a3ca34531 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6144,6 +6144,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| { @@ -6528,6 +6529,7 @@ fn cmdDumpZir( .tree = undefined, .zir = try Zcu.loadZirCache(gpa, f), .mod = undefined, + .mode = .zig, }; defer file.zir.deinit(gpa); @@ -6600,6 +6602,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 d04ef46c9fd4..033ea82de9cb 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -488,7 +488,6 @@ const Writer = struct { .enum_literal, .decl_ref, .decl_val, - .import, .ret_err_value, .ret_err_value_code, .param_anytype, @@ -515,6 +514,8 @@ const Writer = struct { .declaration => try self.writeDeclaration(stream, inst), .extended => try self.writeExtended(stream, inst), + + .import => try self.writeImport(stream, inst), } } @@ -2842,4 +2843,13 @@ const Writer = struct { try stream.writeByte('\n'); } } + + fn writeImport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok; + const extra = self.code.extraData(Zir.Inst.Import, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.res_ty); + const import_path = self.code.nullTerminatedString(extra.path); + try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(import_path)}); + try self.writeSrcTok(stream, inst_data.src_tok); + } }; diff --git a/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 b57a476e8938..ce179da9fad3 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -90,6 +90,11 @@ pub const Case = struct { link_libc: bool = false, pic: ?bool = null, pie: ?bool = null, + /// A list of imports to cache alongside the source file. + imports: []const []const u8 = &.{}, + /// Where to look for imports relative to the `cases_dir_path` given to + /// `lower_to_build_steps`. If null, file imports will assert. + import_path: ?[]const u8 = null, deps: std.ArrayList(DepModule), @@ -413,6 +418,7 @@ fn addFromDirInner( const pic = try manifest.getConfigForKeyAssertSingle("pic", ?bool); const pie = try manifest.getConfigForKeyAssertSingle("pie", ?bool); const emit_bin = try manifest.getConfigForKeyAssertSingle("emit_bin", bool); + const imports = try manifest.getConfigForKeyAlloc(ctx.arena, "imports", []const u8); if (manifest.type == .translate_c) { for (c_frontends) |c_frontend| { @@ -470,7 +476,7 @@ fn addFromDirInner( const next = ctx.cases.items.len; try ctx.cases.append(.{ .name = std.fs.path.stem(filename), - .target = resolved_target, + .import_path = std.fs.path.dirname(filename), .backend = backend, .updates = std.ArrayList(Cases.Update).init(ctx.cases.allocator), .emit_bin = emit_bin, @@ -480,6 +486,8 @@ fn addFromDirInner( .pic = pic, .pie = pie, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), + .imports = imports, + .target = b.resolveTargetQuery(target_query), }); try cases.append(next); } @@ -619,6 +627,7 @@ pub fn lowerToBuildSteps( ) void { const host = std.zig.system.resolveTargetQuery(.{}) catch |err| std.debug.panic("unable to detect native host: {s}\n", .{@errorName(err)}); + const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); for (self.incremental_cases.items) |incr_case| { if (true) { @@ -662,11 +671,21 @@ pub fn lowerToBuildSteps( file_sources.put(file.path, writefiles.add(file.path, file.src)) catch @panic("OOM"); } + for (case.imports) |import_rel| { + const import_abs = std.fs.path.join(b.allocator, &.{ + cases_dir_path, + case.import_path orelse @panic("import_path not set"), + import_rel, + }) catch @panic("OOM"); + _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel); + } + const mod = b.createModule(.{ .root_source_file = root_source_file, .target = case.target, .optimize = case.optimize_mode, }); + if (case.link_libc) mod.link_libc = true; if (case.pic) |pic| mod.pic = pic; for (case.deps.items) |dep| { @@ -962,6 +981,8 @@ const TestManifestConfigDefaults = struct { return "null"; } else if (std.mem.eql(u8, key, "pie")) { return "null"; + } else if (std.mem.eql(u8, key, "imports")) { + return ""; } else unreachable; } }; @@ -998,6 +1019,7 @@ const TestManifest = struct { .{ "backend", {} }, .{ "pic", {} }, .{ "pie", {} }, + .{ "imports", {} }, }); const Type = enum { @@ -1020,7 +1042,7 @@ const TestManifest = struct { fn ConfigValueIterator(comptime T: type) type { return struct { - inner: std.mem.SplitIterator(u8, .scalar), + inner: std.mem.TokenIterator(u8, .scalar), fn next(self: *@This()) !?T { const next_raw = self.inner.next() orelse return null; @@ -1098,7 +1120,9 @@ const TestManifest = struct { // Parse key=value(s) var kv_it = std.mem.splitScalar(u8, trimmed, '='); const key = kv_it.first(); - if (!valid_keys.has(key)) return error.InvalidKey; + if (!valid_keys.has(key)) { + return error.InvalidKey; + } try manifest.config_map.putNoClobber(key, kv_it.next() orelse return error.MissingValuesForConfig); } @@ -1115,7 +1139,7 @@ const TestManifest = struct { ) ConfigValueIterator(T) { const bytes = self.config_map.get(key) orelse TestManifestConfigDefaults.get(self.type, key); return ConfigValueIterator(T){ - .inner = std.mem.splitScalar(u8, bytes, ','), + .inner = std.mem.tokenizeScalar(u8, bytes, ','), }; } @@ -1232,6 +1256,18 @@ const TestManifest = struct { return try getDefaultParser(o.child)(str); } }.parse, + .@"struct" => @compileError("no default parser for " ++ @typeName(T)), + .pointer => { + if (T == []const u8) { + return struct { + fn parse(str: []const u8) anyerror!T { + return str; + } + }.parse; + } else { + @compileError("no default parser for " ++ @typeName(T)); + } + }, else => @compileError("no default parser for " ++ @typeName(T)), } } From 73a37513e1672dc94691379dd1d4976179db30f3 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 16:52:46 -0800 Subject: [PATCH 02/98] 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 12ec31d49e1c499fa4873b6a32717dcad12f2934 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 17:47:08 -0800 Subject: [PATCH 03/98] 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 d2069d870c1bfa501f36e749281560b04b12f4bb Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 4 Nov 2024 19:18:42 -0800 Subject: [PATCH 04/98] 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 37021e09b498954eaa8ec257c3cc5cc0c2bcc92e Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 12:38:31 -0800 Subject: [PATCH 05/98] 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 1100cba77235..60234c12b6db 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13970,8 +13970,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}); @@ -14002,8 +14000,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 7500ba9da3fe3112d0f032c9213c1691ae5a4177 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 13:33:05 -0800 Subject: [PATCH 06/98] 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 1c7e05e0401361b301c1610f1319955044aa34ff Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 17:00:40 -0800 Subject: [PATCH 07/98] 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 1d1d5327f6ceb789251c9f67dd2e1b8b8809f91d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 5 Nov 2024 20:12:43 -0800 Subject: [PATCH 08/98] 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 813b385fc1f87bc4de63f53cc649a8f09d54e12d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 01:47:08 -0800 Subject: [PATCH 09/98] 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 5bcce5ffaa2bd288991e72588077b54fce299f80 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 13:10:32 -0800 Subject: [PATCH 10/98] 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 69ea7cd1c85b4800e004b2583639a01ce3e8878a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 14:13:58 -0800 Subject: [PATCH 11/98] 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 fa02c5db73a3291ac54caecd140efe4ffc6c3393 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 14:29:24 -0800 Subject: [PATCH 12/98] 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 321bea62d588d32c251f596dbeb5cab92ba60b4c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 6 Nov 2024 15:28:59 -0800 Subject: [PATCH 13/98] 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 36768b1a6f7217141d8365167625c47715d86311 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 11 Nov 2024 16:48:30 -0800 Subject: [PATCH 14/98] 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 e6c4a1736207983840f8a638904896c679cc9d02 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 11 Nov 2024 16:57:27 -0800 Subject: [PATCH 15/98] 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 b8af3acecd444308c90dbb4e921c0d63d1dc76b2 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 12 Nov 2024 19:26:26 -0800 Subject: [PATCH 16/98] 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 0ad12f3bf567de27f03c59c9d37cbd850940ba9b Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 12 Nov 2024 20:39:31 -0800 Subject: [PATCH 17/98] 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 049a58f9193c17facc8a67ba6e1500445b57d888 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 13 Nov 2024 14:50:57 -0800 Subject: [PATCH 18/98] 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 8d948f4cac5f08d21575555a39d3b5f6bb0e6d52 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 26 Nov 2024 14:41:24 -0800 Subject: [PATCH 19/98] 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 e2b5cc3ce5e1849cd69c19a6264cd0b26d9462ba Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 26 Nov 2024 16:15:45 -0800 Subject: [PATCH 20/98] 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 f23165ade443d2df75c3521879a233af59977745 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 27 Nov 2024 15:16:39 -0800 Subject: [PATCH 21/98] 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 60234c12b6db..d2a03ff25d2c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14015,6 +14015,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 fbd4995c8532431e4f55570fe23b68d1277fd3f9 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 18:32:06 -0800 Subject: [PATCH 22/98] 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 ec6ca0027c4ced3c17e7044d6fae96856258e2e4 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 18:49:00 -0800 Subject: [PATCH 23/98] 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 ad3d5fb6a405e67684ab96a73075bbc5d8be8cac Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 19:20:54 -0800 Subject: [PATCH 24/98] 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 ea05fea9fe8bff45c25fca07cc298431df669fc8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 2 Dec 2024 23:35:57 -0800 Subject: [PATCH 25/98] 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 98ac319d464d9b83d5aa72a21cab302ec3c69d91 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 17 Dec 2024 16:11:17 -0800 Subject: [PATCH 26/98] 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 1ba4e607a1319dfbf9b75f9a1be84ab29d3d453f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 17 Dec 2024 20:36:34 -0800 Subject: [PATCH 27/98] 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 8fdb3c63113cc80aeb7d0dc77c921c4d5d1082dc Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 20 Dec 2024 21:41:42 -0800 Subject: [PATCH 28/98] 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 c6ec16f2d449ea33a9106f7b287bdb7a8504c213 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 15:04:32 -0800 Subject: [PATCH 29/98] 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 32df4a9e8167..5aa3520666ed 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3209,10 +3209,16 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { if (error_msg) |msg| { try addModuleErrorMsg(zcu, &bundle, msg.*); } else { - // Must be ZIR errors. Note that this may include AST errors. - // addZirErrorMessages asserts that the tree is loaded. - _ = try file.getTree(gpa); - try addZirErrorMessages(&bundle, file); + // Must be ZIR or Zoir errors. Note that this may include AST errors. + _ = try file.getTree(gpa); // Tree must be loaded. + if (file.zir_loaded) { + try addZirErrorMessages(&bundle, file); + } else if (file.zoir != null) { + try addZoirErrorMessages(&bundle, file); + } else { + // Either Zir or Zoir must have been loaded. + unreachable; + } } } var sorted_failed_analysis: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, *Zcu.ErrorMsg).DataList.Slice = s: { @@ -3626,6 +3632,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 ada402291409..5e67338e7807 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -39,6 +39,8 @@ const AnalUnit = InternPool.AnalUnit; const BuiltinFn = std.zig.BuiltinFn; const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); +const Zoir = std.zig.Zoir; +const ZonGen = std.zig.ZonGen; comptime { @setEvalBranchQuota(4000); @@ -672,6 +674,8 @@ pub const File = struct { tree: Ast, /// Whether this is populated or not depends on `zir_loaded`. zir: Zir, + /// Cached Zoir, generated lazily. + zoir: ?Zoir = null, /// Module that this file is a part of, managed externally. mod: *Package.Module, /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen. @@ -719,6 +723,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); @@ -797,6 +802,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 4c2fef39d3c276dd1b2bca97f97cebe4ddbb6ceb Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 16:19:36 -0800 Subject: [PATCH 30/98] 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 494679b7cc4d2d76a5d62539a6ad51f3afa6f7d3 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 18:08:17 -0800 Subject: [PATCH 31/98] 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 d86851620584ff3afcd6203c70b54a083b8207c8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 21 Dec 2024 21:18:06 -0800 Subject: [PATCH 32/98] 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 cf4691a6675e..4ed64540ee03 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1585,7 +1585,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 cfd467f9b1f3..bf7d0be2a763 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 c7c10defb7cc..849f367e3a5d 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8924,22 +8924,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( @@ -11548,20 +11562,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( @@ -11679,7 +11682,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; @@ -11808,17 +11811,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 988a071b45ab..22727adbd925 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1065,11 +1065,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 5e67338e7807..fad05bf7a8b1 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -689,7 +689,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 e38b7a22a5f5b16ddec3222ad2b88ad4bf3b32be Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:15:46 -0800 Subject: [PATCH 33/98] 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 3e9109dd1ac5..01558265025c 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) ++ "'"), } } @@ -2775,11 +2799,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 3668228e07bc..4a6b5a86d2a7 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -4389,7 +4389,7 @@ pub const LoadedEnumType = struct { // Auto-numbered enum. Convert `int_tag_val` to field index. const field_index = switch (ip.indexToKey(int_tag_val).int.storage) { inline .u64, .i64 => |x| std.math.cast(u32, x) orelse return null, - .big_int => |x| x.to(u32) catch return null, + .big_int => |x| x.toInt(u32) catch return null, .lazy_align, .lazy_size => unreachable, }; return if (field_index < self.names.len) field_index else null; @@ -7957,7 +7957,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u8, - .data = big_int.to(u8) catch unreachable, + .data = big_int.toInt(u8) catch unreachable, }); break :b; }, @@ -7974,7 +7974,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u16, - .data = big_int.to(u16) catch unreachable, + .data = big_int.toInt(u16) catch unreachable, }); break :b; }, @@ -7991,7 +7991,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All .big_int => |big_int| { items.appendAssumeCapacity(.{ .tag = .int_u32, - .data = big_int.to(u32) catch unreachable, + .data = big_int.toInt(u32) catch unreachable, }); break :b; }, @@ -8006,7 +8006,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .i32_type => switch (int.storage) { .big_int => |big_int| { - const casted = big_int.to(i32) catch unreachable; + const casted = big_int.toInt(i32) catch unreachable; items.appendAssumeCapacity(.{ .tag = .int_i32, .data = @as(u32, @bitCast(casted)), @@ -8024,7 +8024,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .usize_type => switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_usize, .data = casted, @@ -8045,14 +8045,14 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All }, .comptime_int_type => switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_comptime_int_u32, .data = casted, }); break :b; } else |_| {} - if (big_int.to(i32)) |casted| { + if (big_int.toInt(i32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_comptime_int_i32, .data = @as(u32, @bitCast(casted)), @@ -8082,7 +8082,7 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All } switch (int.storage) { .big_int => |big_int| { - if (big_int.to(u32)) |casted| { + if (big_int.toInt(u32)) |casted| { items.appendAssumeCapacity(.{ .tag = .int_small, .data = try addExtra(extra, IntSmall{ diff --git a/src/Value.zig b/src/Value.zig index c8468c1661bc..f9f552aa0f42 100644 --- a/src/Value.zig +++ b/src/Value.zig @@ -270,7 +270,7 @@ pub fn getUnsignedIntInner( else => switch (zcu.intern_pool.indexToKey(val.toIntern())) { .undef => unreachable, .int => |int| switch (int.storage) { - .big_int => |big_int| big_int.to(u64) catch null, + .big_int => |big_int| big_int.toInt(u64) catch null, .u64 => |x| x, .i64 => |x| std.math.cast(u64, x), .lazy_align => |ty| (try Type.fromInterned(ty).abiAlignmentInner(strat.toLazy(), zcu, tid)).scalar.toByteUnits() orelse 0, @@ -311,7 +311,7 @@ pub fn toSignedInt(val: Value, zcu: *const Zcu) i64 { .bool_true => 1, else => switch (zcu.intern_pool.indexToKey(val.toIntern())) { .int => |int| switch (int.storage) { - .big_int => |big_int| big_int.to(i64) catch unreachable, + .big_int => |big_int| big_int.toInt(i64) catch unreachable, .i64 => |x| x, .u64 => |x| @intCast(x), .lazy_align => |ty| @intCast(Type.fromInterned(ty).abiAlignment(zcu).toByteUnits() orelse 0), @@ -898,7 +898,7 @@ pub fn readFromPackedMemory( pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T { return switch (zcu.intern_pool.indexToKey(val.toIntern())) { .int => |int| switch (int.storage) { - .big_int => |big_int| @floatCast(bigIntToFloat(big_int.limbs, big_int.positive)), + .big_int => |big_int| big_int.toFloat(T), inline .u64, .i64 => |x| { if (T == f80) { @panic("TODO we can't lower this properly on non-x86 llvm backend yet"); @@ -915,25 +915,6 @@ pub fn toFloat(val: Value, comptime T: type, zcu: *Zcu) T { }; } -/// TODO move this to std lib big int code -fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 { - if (limbs.len == 0) return 0; - - const base = std.math.maxInt(std.math.big.Limb) + 1; - var result: f128 = 0; - var i: usize = limbs.len; - while (i != 0) { - i -= 1; - const limb: f128 = @floatFromInt(limbs[i]); - result = @mulAdd(f128, base, result, limb); - } - if (positive) { - return result; - } else { - return -result; - } -} - pub fn clz(val: Value, ty: Type, zcu: *Zcu) u64 { var bigint_buf: BigIntSpace = undefined; const bigint = val.toBigInt(&bigint_buf, zcu); @@ -1548,7 +1529,7 @@ pub fn floatFromIntScalar(val: Value, float_ty: Type, pt: Zcu.PerThread, comptim .undef => try pt.undefValue(float_ty), .int => |int| switch (int.storage) { .big_int => |big_int| { - const float = bigIntToFloat(big_int.limbs, big_int.positive); + const float = big_int.toFloat(f128); return pt.floatValue(float_ty, float); }, inline .u64, .i64 => |x| floatFromIntInner(x, float_ty, pt), @@ -4583,7 +4564,7 @@ pub fn interpret(val: Value, comptime T: type, pt: Zcu.PerThread) error{ OutOfMe .int => switch (ip.indexToKey(val.toIntern()).int.storage) { .lazy_align, .lazy_size => unreachable, // `val` is fully resolved inline .u64, .i64 => |x| std.math.cast(T, x) orelse return error.TypeMismatch, - .big_int => |big| big.to(T) catch return error.TypeMismatch, + .big_int => |big| big.toInt(T) catch return error.TypeMismatch, }, .float => val.toFloat(T, zcu), diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index b0869f3a8f35..f79b5700a8f8 100644 --- a/src/codegen/llvm/Builder.zig +++ b/src/codegen/llvm/Builder.zig @@ -13776,8 +13776,8 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co }; const bit_count = extra.type.scalarBits(self); const val: i64 = if (bit_count <= 64) - bigint.to(i64) catch unreachable - else if (bigint.to(u64)) |val| + bigint.toInt(i64) catch unreachable + else if (bigint.toInt(u64)) |val| @bitCast(val) else |_| { const limbs = try record.addManyAsSlice( @@ -14276,9 +14276,9 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co else => unreachable, }, }; - const val: i64 = if (bigint.to(i64)) |val| + const val: i64 = if (bigint.toInt(i64)) |val| val - else |_| if (bigint.to(u64)) |val| + else |_| if (bigint.toInt(u64)) |val| @bitCast(val) else |_| { const limbs_len = std.math.divCeil(u32, extra.bit_width, 64) catch unreachable; diff --git a/src/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 502b1892e21a8ac004ea393003ca2a4587f38207 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:34:35 -0800 Subject: [PATCH 34/98] 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 bf7d0be2a763..3f69ce5aeb6b 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 5aa3520666ed..08e4f8a2d2c8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2221,7 +2221,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); } } @@ -4289,7 +4289,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(); @@ -4343,7 +4343,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 29e5dad7c528..5b3a487a4939 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -492,7 +492,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 d2a03ff25d2c..f3dd2573084d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13985,7 +13985,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 fad05bf7a8b1..7a60340d1b58 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -689,9 +689,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, @@ -711,10 +708,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 @@ -797,7 +794,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 42ce8635b444..d564ef8da5d6 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -1867,7 +1867,7 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const zcu = pt.zcu; const gpa = zcu.gpa; const file = zcu.fileByIndex(file_index); - assert(file.mode == .zig); + assert(file.getMode() == .zig); assert(zcu.fileRootType(file_index) == .none); if (file.status != .success_zir) { @@ -1993,7 +1993,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 }); @@ -2107,7 +2106,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 f79a3ca34531..205ce46d0aa9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6144,7 +6144,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| { @@ -6529,7 +6528,6 @@ fn cmdDumpZir( .tree = undefined, .zir = try Zcu.loadZirCache(gpa, f), .mod = undefined, - .mode = .zig, }; defer file.zir.deinit(gpa); @@ -6602,7 +6600,6 @@ fn cmdChangelist( .tree = undefined, .zir = undefined, .mod = undefined, - .mode = Zcu.File.modeFromPath(old_source_file), }; file.mod = try Package.Module.createLimited(arena, .{ From 1a5eb9ca82711f95a706f6b7c0dcd781c2158c8a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 14:38:22 -0800 Subject: [PATCH 35/98] 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 3ab980fa554964ca539e5163873cbf44360f304d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 4 Jan 2025 22:24:10 -0800 Subject: [PATCH 36/98] 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 f3dd2573084d..ee7b59e440a3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5791,7 +5791,7 @@ fn addNullTerminatedStrLit(sema: *Sema, string: InternPool.NullTerminatedString) return sema.addStrLit(string.toString(), string.length(&sema.pt.zcu.intern_pool)); } -fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref { +pub fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref { const pt = sema.pt; const array_ty = try pt.arrayType(.{ .len = len, @@ -14016,6 +14016,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 223ef9e5646a46bef47c686f86b02f3a48c5abaf Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:14:33 -0800 Subject: [PATCH 37/98] 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 7a60340d1b58..afed23f91b24 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -3492,8 +3492,6 @@ pub fn atomicPtrAlignment( } /// Returns null in the following cases: -/// * `@TypeOf(.{})` -/// * A struct which has no fields (`struct {}`). /// * Not a struct. pub fn typeToStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType { if (ty.ip_index == .none) return null; From 14c33cd547532eb2a83f7fe17881f0ad17d1700e Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:20:13 -0800 Subject: [PATCH 38/98] 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 01558265025c..00c7c7439e64 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. @@ -2799,6 +2802,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. @@ -2807,7 +2813,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 1e0fb82e61e08f2d246986ec70139da8e1138165 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 13:28:31 -0800 Subject: [PATCH 39/98] 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 ac083da4647c10abf29a25157fbc7d5b5a67ac8c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 16:05:20 -0800 Subject: [PATCH 40/98] 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 83eeb9f6de5e229e91284ad64dbdb964fac84b1b Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 5 Jan 2025 17:57:45 -0800 Subject: [PATCH 41/98] 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 afed23f91b24..54c4ee530b8a 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -803,7 +803,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 205ce46d0aa9..d47d3b1a53f2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6278,7 +6278,7 @@ fn cmdAstCheck( } }, .zon => { - const zoir = try ZonGen.generate(gpa, file.tree); + const zoir = try ZonGen.generate(gpa, file.tree, 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 97c42a0779936509f76d39901b7c1988d5b27188 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 18:51:12 -0800 Subject: [PATCH 42/98] 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 849f367e3a5d..307983fafdf9 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -11562,9 +11562,21 @@ fn parseStrLit( } } -fn failWithStrLitError(astgen: *AstGen, err: std.zig.string_literal.Error, token: Ast.TokenIndex, bytes: []const u8, offset: u32) InnerError { +fn failWithStrLitError( + astgen: *AstGen, + err: std.zig.string_literal.Error, + token: Ast.TokenIndex, + bytes: []const u8, + offset: u32, +) InnerError { const raw_string = bytes[offset..]; - return err.lower(raw_string, offset, AstGen.failOff, .{ astgen, token }); + return 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 d770aef02821dc78ef9170bf0bb846325b32edb2 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 19:00:48 -0800 Subject: [PATCH 43/98] 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 54c4ee530b8a..5c62207fa1f0 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -803,7 +803,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 d47d3b1a53f2..ba5ebf8efd09 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6278,7 +6278,7 @@ fn cmdAstCheck( } }, .zon => { - const zoir = try ZonGen.generate(gpa, file.tree, true); + const zoir = try ZonGen.generate(gpa, file.tree, .{}); defer zoir.deinit(gpa); if (zoir.hasCompileErrors()) { From f3bf6c68b73cb58b3244ca818186e1fe868eede0 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 19:16:57 -0800 Subject: [PATCH 44/98] 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 ef65dd5c7b1da28880ca7775d271b6fc3f730a8d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 20:43:32 -0800 Subject: [PATCH 45/98] 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 5c62207fa1f0..507452dda34b 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -799,11 +799,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 e673fc06648b99edd91ed9ef47d6b7805756f27f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 21:29:14 -0800 Subject: [PATCH 46/98] 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 82d6faa3772dfc25ae343af52fea6c8e6afa40c7 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 22:34:44 -0800 Subject: [PATCH 47/98] 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 f25b957a216981520c8124f269a162d3b8bf5071 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 23:02:06 -0800 Subject: [PATCH 48/98] 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 0191688a69310020d1ab0bd1151e294b85d9569d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 7 Jan 2025 23:06:58 -0800 Subject: [PATCH 49/98] 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 454176c07ae8012872afb85601ca9161c218e56d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 9 Jan 2025 17:30:55 -0800 Subject: [PATCH 50/98] 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 4ed64540ee03..c8f5103626c4 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1584,7 +1584,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 4bf651d2d8484982f8e3fea19bf70f4c2d3be76e Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 9 Jan 2025 23:08:27 -0800 Subject: [PATCH 51/98] 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)).?); From af3f6ae798e3e40763340a34b1f178e7ec495853 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 23 Jan 2025 16:30:27 -0800 Subject: [PATCH 52/98] Resolves some review feedback --- lib/std/math/big/int.zig | 33 ++++++++----------- lib/std/std.zig | 2 +- lib/std/zig/AstGen.zig | 2 +- lib/std/zig/Zir.zig | 2 +- lib/std/zig/ZonGen.zig | 1 - lib/std/zig/number_literal.zig | 5 --- lib/std/zon.zig | 4 +-- src/Compilation.zig | 1 + src/Sema.zig | 4 +-- src/zon.zig | 18 ++-------- .../compile_errors/@import_zon_bad_import.zig | 1 - .../compile_errors/@import_zon_no_rt.zig | 2 +- .../cases/compile_errors/@import_zon_void.zig | 1 - test/src/Cases.zig | 2 +- 14 files changed, 27 insertions(+), 51 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 00c7c7439e64..0af1a052b60f 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2225,25 +2225,20 @@ pub const Const = struct { /// 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) ++ "'"), + 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); } } diff --git a/lib/std/std.zig b/lib/std/std.zig index 20543b67d893..5c997aebaf2e 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -44,7 +44,6 @@ 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"); @@ -94,6 +93,7 @@ pub const valgrind = @import("valgrind.zig"); pub const wasm = @import("wasm.zig"); pub const zig = @import("zig.zig"); pub const zip = @import("zip.zig"); +pub const zon = @import("zon.zig"); pub const start = @import("start.zig"); const root = @import("root"); diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 307983fafdf9..b2cc85b98b67 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -11570,7 +11570,7 @@ fn failWithStrLitError( offset: u32, ) InnerError { const raw_string = bytes[offset..]; - return AstGen.failOff( + return failOff( astgen, token, @intCast(offset + err.offset()), diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig index 3896e23778a5..092a354de6f8 100644 --- a/lib/std/zig/Zir.zig +++ b/lib/std/zig/Zir.zig @@ -483,7 +483,7 @@ pub const Inst = struct { /// Uses the `pl_node` union field. `payload_index` points to a `FuncFancy`. func_fancy, /// Implements the `@import` builtin. - /// Uses the `str_tok` field. + /// Uses the `pl_tok` field. import, /// Integer literal that fits in a u64. Uses the `int` union field. int, diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index d2a17f263778..e0da15865052 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -270,7 +270,6 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator 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", .{}); diff --git a/lib/std/zig/number_literal.zig b/lib/std/zig/number_literal.zig index 5ff027a43f67..a4dc33eb91c3 100644 --- a/lib/std/zig/number_literal.zig +++ b/lib/std/zig/number_literal.zig @@ -60,11 +60,6 @@ pub const Error = union(enum) { period_after_exponent: usize, }; -const FormatWithSource = struct { - bytes: []const u8, - err: Error, -}; - /// 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/zon.zig b/lib/std/zon.zig index 3e53a5f456ee..3f73b1936dd1 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -17,7 +17,7 @@ //! * anonymous tuple literals //! //! Here is an example ZON object: -//! ```zon +//! ``` //! .{ //! .a = 1.5, //! .b = "hello, world!", @@ -27,7 +27,7 @@ //! ``` //! //! Individual primitives are also valid ZON, for example: -//! ```zon +//! ``` //! "This string is a valid ZON object." //! ``` //! diff --git a/src/Compilation.zig b/src/Compilation.zig index 08e4f8a2d2c8..dc1d7df320f4 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3634,6 +3634,7 @@ pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { pub fn addZoirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { assert(file.source_loaded); + assert(file.tree_loaded); const gpa = eb.gpa; const src_path = try file.fullPath(gpa); defer gpa.free(src_path); diff --git a/src/Sema.zig b/src/Sema.zig index ee7b59e440a3..8e148d2e446d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14002,12 +14002,12 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. }; if (extra.res_ty == .none) { - return sema.fail(block, operand_src, "import ZON must have a known result type", .{}); + return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); } const res_ty_inst = try sema.resolveInst(extra.res_ty); const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst); if (res_ty.isGenericPoison()) { - return sema.fail(block, operand_src, "import ZON must have a known result type", .{}); + return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); } const interned = try zon.lower( diff --git a/src/zon.zig b/src/zon.zig index c6bc617ed89d..77414bb23fe0 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -71,18 +71,6 @@ fn fail( return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } -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 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), @@ -502,7 +490,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 self.sema.arena.alloc(InternPool.Index, field_types.len); - for (elems) |*v| v.* = .none; + @memset(elems, .none); for (0..elem_nodes.len) |i| { if (i >= elems.len) { @@ -570,7 +558,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 self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); - for (field_values) |*v| v.* = .none; + @memset(field_values, .none); for (0..fields.names.len) |i| { const field_name = try ip.getOrPutString( @@ -627,7 +615,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. for (field_values, field_names) |*value, name| { if (value.* == .none) return self.fail( .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "missing field {}", + "missing field '{}'", .{name.fmt(ip)}, ); } diff --git a/test/cases/compile_errors/@import_zon_bad_import.zig b/test/cases/compile_errors/@import_zon_bad_import.zig index b992506f74f0..4df03ce2dbfb 100644 --- a/test/cases/compile_errors/@import_zon_bad_import.zig +++ b/test/cases/compile_errors/@import_zon_bad_import.zig @@ -5,6 +5,5 @@ export fn entry() void { } // error -// target=native // // :3:9: error: unable to open 'bogus-does-not-exist.zon': FileNotFound diff --git a/test/cases/compile_errors/@import_zon_no_rt.zig b/test/cases/compile_errors/@import_zon_no_rt.zig index b4d1dc1228e9..b7518b0cb1f6 100644 --- a/test/cases/compile_errors/@import_zon_no_rt.zig +++ b/test/cases/compile_errors/@import_zon_no_rt.zig @@ -6,4 +6,4 @@ export fn entry() void { // error // imports=zon/simple_union.zon // -// tmp.zig:2:23: error: import ZON must have a known result type +// tmp.zig:2:23: error: '@import' of ZON must have a known result type diff --git a/test/cases/compile_errors/@import_zon_void.zig b/test/cases/compile_errors/@import_zon_void.zig index 327fe74303f5..3f4f2e5481e3 100644 --- a/test/cases/compile_errors/@import_zon_void.zig +++ b/test/cases/compile_errors/@import_zon_void.zig @@ -8,4 +8,3 @@ export fn entry() void { // // 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/src/Cases.zig b/test/src/Cases.zig index ce179da9fad3..db1a564373ec 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -487,7 +487,7 @@ fn addFromDirInner( .pie = pie, .deps = std.ArrayList(DepModule).init(ctx.cases.allocator), .imports = imports, - .target = b.resolveTargetQuery(target_query), + .target = resolved_target, }); try cases.append(next); } From 55bf1f496f037815282a58c8a764b60776011955 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 23 Jan 2025 18:07:44 -0800 Subject: [PATCH 53/98] Updates some field/enum names after rebase --- lib/std/zon/parse.zig | 26 +++++++++++++------------- lib/std/zon/stringify.zig | 10 +++++----- src/zon.zig | 4 ++-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 6904b95c88cd..fad4be451451 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -331,10 +331,10 @@ pub fn free(gpa: Allocator, value: anytype) void { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { switch (pointer.size) { - .One, .Many, .C => if (comptime requiresAllocator(Value)) { + .one, .many, .c => if (comptime requiresAllocator(Value)) { @compileError(@typeName(Value) ++ ": free cannot free non slice pointers"); }, - .Slice => for (value) |item| { + .slice => for (value) |item| { free(gpa, item); }, } @@ -508,7 +508,7 @@ const Parser = struct { 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; + if (pointer.sentinel_ptr != null) size_hint += 1; var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint); defer buf.deinit(self.gpa); @@ -522,15 +522,15 @@ const Parser = struct { } if (pointer.child != u8 or - pointer.size != .Slice or + pointer.size != .slice or !pointer.is_const or - (pointer.sentinel != null and @as(*const u8, @ptrCast(pointer.sentinel)).* != 0) or + (pointer.sentinel_ptr != null and @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* != 0) or pointer.alignment != 1) { return self.failExpectedContainer(T, node); } - if (pointer.sentinel != null) { + if (pointer.sentinel_ptr != null) { return try buf.toOwnedSliceSentinel(self.gpa, 0); } else { return try buf.toOwnedSlice(self.gpa); @@ -547,12 +547,12 @@ const Parser = struct { // Make sure we're working with a slice switch (pointer.size) { - .Slice => {}, - .One, .Many, .C => @compileError(@typeName(T) ++ ": non slice pointers not supported"), + .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 sentinel = if (pointer.sentinel_ptr) |s| @as(*const pointer.child, @ptrCast(s)).* else null; const slice = try self.gpa.allocWithOptions( pointer.child, nodes.len, @@ -706,7 +706,7 @@ const Parser = struct { inline for (field_found, 0..) |found, i| { if (!found) { const field_info = field_infos[i]; - if (field_info.default_value) |default| { + if (field_info.default_value_ptr) |default| { const typed: *const field_info.type = @ptrCast(@alignCast(default)); @field(result, field_info.name) = typed.*; } else { @@ -748,7 +748,7 @@ const Parser = struct { 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| { + if (field_infos[i].default_value_ptr) |default| { const typed: *const field_infos[i].type = @ptrCast(@alignCast(default)); @field(result, field_infos[i].name) = typed.*; } else { @@ -981,9 +981,9 @@ const Parser = struct { .array => return self.failNode(node, "expected tuple"), .pointer => |pointer| { if (pointer.child == u8 and - pointer.size == .Slice and + pointer.size == .slice and pointer.is_const and - (pointer.sentinel == null or @as(*const u8, @ptrCast(pointer.sentinel)).* == 0) and + (pointer.sentinel_ptr == null or @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* == 0) and pointer.alignment == 1) { return self.failNode(node, "expected string"); diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 8cf5ee5702f3..e4d94f5f9757 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -166,11 +166,11 @@ fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void { switch (@typeInfo(@TypeOf(val))) { .pointer => |pointer| switch (pointer.size) { - .One => try checkValueDepth(val.*, child_depth), - .Slice => for (val) |item| { + .one => try checkValueDepth(val.*, child_depth), + .slice => for (val) |item| { try checkValueDepth(item, child_depth); }, - .C, .Many => {}, + .c, .many => {}, }, .array => for (val) |item| { try checkValueDepth(item, child_depth); @@ -373,7 +373,7 @@ pub fn Serializer(Writer: type) type { .pointer => |pointer| { const child_type = switch (@typeInfo(pointer.child)) { .array => |array| array.child, - else => if (pointer.size != .Slice) @compileError( + else => if (pointer.size != .slice) @compileError( @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", ) else pointer.child, }; @@ -408,7 +408,7 @@ pub fn Serializer(Writer: type) type { 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| { + if (field_info.default_value_ptr) |default_field_value_opaque| { const field_value = @field(val, field_info.name); const default_field_value: *const @TypeOf(field_value) = @ptrCast( @alignCast(default_field_value_opaque), diff --git a/src/zon.zig b/src/zon.zig index 77414bb23fe0..8a549cb109bb 100644 --- a/src/zon.zig +++ b/src/zon.zig @@ -631,7 +631,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); - if (ptr_info.flags.size != .Slice) { + if (ptr_info.flags.size != .slice) { return self.fail( .{ .node_abs = node.getAstNode(self.file.zoir.?) }, "non slice pointers are not available in ZON", @@ -695,7 +695,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool .sentinel = ptr_info.sentinel, .flags = b: { var flags = ptr_info.flags; - flags.size = .Many; + flags.size = .many; break :b flags; }, .packed_offset = ptr_info.packed_offset, From a9e9612ec53fa2d3bf0efb63ea52d4801d734ec4 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 23 Jan 2025 18:27:46 -0800 Subject: [PATCH 54/98] Resolves more feedback --- lib/std/zon/parse.zig | 2 +- src/Sema.zig | 2 +- src/{zon.zig => Sema/LowerZon.zig} | 30 +++++++++++++++--------------- 3 files changed, 17 insertions(+), 17 deletions(-) rename src/{zon.zig => Sema/LowerZon.zig} (97%) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index fad4be451451..8324ea83bb37 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -626,7 +626,7 @@ const Parser = struct { node: Zoir.Node.Index, ) !T { const repr = node.get(self.zoir); - const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (repr) { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return self.failExpectedContainer(T, node), diff --git a/src/Sema.zig b/src/Sema.zig index 8e148d2e446d..8eb3be64d723 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -187,7 +187,7 @@ const Alignment = InternPool.Alignment; const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; const Cache = std.Build.Cache; -const zon = @import("zon.zig"); +const zon = @import("Sema/LowerZon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; diff --git a/src/zon.zig b/src/Sema/LowerZon.zig similarity index 97% rename from src/zon.zig rename to src/Sema/LowerZon.zig index 8a549cb109bb..4a27127bdae1 100644 --- a/src/zon.zig +++ b/src/Sema/LowerZon.zig @@ -1,9 +1,9 @@ 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 Zcu = @import("../Zcu.zig"); +const Sema = @import("../Sema.zig"); +const Air = @import("../Air.zig"); +const InternPool = @import("../InternPool.zig"); +const Type = @import("../Type.zig"); const Zir = std.zig.Zir; const AstGen = std.zig.AstGen; const CompileError = Zcu.CompileError; @@ -72,7 +72,7 @@ fn fail( } fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { - switch (Type.zigTypeTag(res_ty, self.sema.pt.zcu)) { + switch (res_ty.zigTypeTag(self.sema.pt.zcu)) { .bool => return self.lowerBool(node), .int, .comptime_int => return self.lowerInt(node, res_ty), .float, .comptime_float => return self.lowerFloat(node, res_ty), @@ -129,7 +129,7 @@ fn lowerInt( 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) { + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const lhs_info = res_ty.intInfo(self.sema.pt.zcu); // If lhs is unsigned and rhs is less than 0, we're out of bounds @@ -168,7 +168,7 @@ fn lowerInt( } }); }, .big => |val| { - if (Type.zigTypeTag(res_ty, self.sema.pt.zcu) == .int) { + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const int_info = res_ty.intInfo(self.sema.pt.zcu); if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) { return self.fail( @@ -227,7 +227,7 @@ fn lowerInt( .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) { + if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const lhs_info = res_ty.intInfo(self.sema.pt.zcu); // If lhs has less than 64 bits, we bounds check. We check at 64 instead of 32 in // case LHS is signed. @@ -504,7 +504,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I }, ); } - elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), Type.fromInterned(field_types[i])); + elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); @@ -546,7 +546,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. 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.?)) { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, else => return self.fail( @@ -577,7 +577,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]); + const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]); if (field_values[name_index] != .none) { const field_node_ast = field_node.getAstNode(self.file.zoir.?); const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; @@ -672,7 +672,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool 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)); + elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child)); } if (ptr_info.sentinel != .none) { @@ -740,7 +740,7 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I break :b .{ field_name, null }; }, .struct_literal => b: { - const fields: std.meta.fieldInfo(Zoir.Node, .struct_literal).type = switch (node.get(self.file.zoir.?)) { + const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, else => return self.fail( .{ .node_abs = node.getAstNode(self.file.zoir.?) }, @@ -791,7 +791,7 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I .ty = union_info.enum_tag_ty, .int = tag_int, } }); - const field_type = Type.fromInterned(union_info.field_types.get(ip)[name_index]); + const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]); const val = if (maybe_field_node) |field_node| b: { if (field_type.toIntern() == .void_type) { return self.fail( From 4eeaa58d63cc44e705fa30f03f95172185cf0e54 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 24 Jan 2025 17:29:33 -0800 Subject: [PATCH 55/98] Fixes float type lowering, disallows inf/nan for comptime floats --- src/Sema/LowerZon.zig | 112 +++++------------- test/behavior/zon.zig | 12 +- .../@import_zon_comptime_inf.zig | 10 ++ .../@import_zon_comptime_nan.zig | 10 ++ .../@import_zon_comptime_neg_inf.zig | 10 ++ test/cases/compile_errors/zon/inf.zon | 1 + test/cases/compile_errors/zon/nan.zon | 1 + test/cases/compile_errors/zon/neg_inf.zon | 1 + 8 files changed, 71 insertions(+), 86 deletions(-) create mode 100644 test/cases/compile_errors/@import_zon_comptime_inf.zig create mode 100644 test/cases/compile_errors/@import_zon_comptime_nan.zig create mode 100644 test/cases/compile_errors/@import_zon_comptime_neg_inf.zig create mode 100644 test/cases/compile_errors/zon/inf.zon create mode 100644 test/cases/compile_errors/zon/nan.zon create mode 100644 test/cases/compile_errors/zon/neg_inf.zon diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 4a27127bdae1..f3c9f62ff1c9 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -212,7 +212,7 @@ fn lowerInt( 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 '{}'", + "type '{}' cannot represent integer value '{}'", .{ val, res_ty.fmt(self.sema.pt) }, ); } @@ -268,92 +268,44 @@ fn lowerFloat( res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - switch (node.get(self.file.zoir.?)) { + const value = switch (node.get(self.file.zoir.?)) { .int_literal => |int| switch (int) { - .small => |val| return self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = @floatFromInt(val) }, - .f32_type => .{ .f32 = @floatFromInt(val) }, - .f64_type => .{ .f64 = @floatFromInt(val) }, - .f80_type => .{ .f80 = @floatFromInt(val) }, - .f128_type, .comptime_float_type => .{ .f128 = @floatFromInt(val) }, - else => unreachable, - }, - } }), - .big => |val| return self.sema.pt.intern(.{ .float = .{ - .ty = res_ty.toIntern(), - .storage = switch (res_ty.toIntern()) { - .f16_type => .{ .f16 = val.toFloat(f16) }, - .f32_type => .{ .f32 = val.toFloat(f32) }, - .f64_type => .{ .f64 = val.toFloat(f64) }, - .f80_type => .{ .f80 = val.toFloat(f80) }, - .f128_type, .comptime_float_type => .{ .f128 = val.toFloat(f128) }, - else => unreachable, - }, - } }), + .small => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), + .big => |val| try self.sema.pt.floatValue(res_ty, val.toFloat(f128)), + }, + .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), + .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), + .pos_inf => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128)); + }, + .neg_inf => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128)); + }, + .nan => b: { + if (res_ty.toIntern() == .comptime_float_type) return self.fail( + .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + "expected type '{}'", + .{res_ty.fmt(self.sema.pt)}, + ); + break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128)); }, - .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)}, ), - } + }; + return value.toIntern(); } fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 123d2b180f24..1d14e4608845 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -391,17 +391,17 @@ test "floats" { } test "inf and nan" { - // comptime float + // f32 { - const actual: struct { comptime_float, comptime_float, comptime_float } = @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.isPositiveInf(@as(f128, @floatCast(actual[1])))); - try expect(std.math.isNegativeInf(@as(f128, @floatCast(actual[2])))); + try expect(std.math.isPositiveInf(actual[1])); + try expect(std.math.isNegativeInf(actual[2])); } - // f32 + // f128 { - const actual: struct { f32, f32, f32 } = @import("zon/inf_and_nan.zon"); + const actual: struct { f128, f128, f128 } = @import("zon/inf_and_nan.zon"); try expect(std.math.isNan(actual[0])); try expect(std.math.isPositiveInf(actual[1])); try expect(std.math.isNegativeInf(actual[2])); diff --git a/test/cases/compile_errors/@import_zon_comptime_inf.zig b/test/cases/compile_errors/@import_zon_comptime_inf.zig new file mode 100644 index 000000000000..0ce8f3681dbc --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_inf.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/inf.zon"); + _ = f; +} + +// error +// imports=zon/inf.zon +// +// inf.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here diff --git a/test/cases/compile_errors/@import_zon_comptime_nan.zig b/test/cases/compile_errors/@import_zon_comptime_nan.zig new file mode 100644 index 000000000000..3c2071523922 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_nan.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/nan.zon"); + _ = f; +} + +// error +// imports=zon/nan.zon +// +// nan.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here diff --git a/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig b/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig new file mode 100644 index 000000000000..dfa665ed193d --- /dev/null +++ b/test/cases/compile_errors/@import_zon_comptime_neg_inf.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: comptime_float = @import("zon/neg_inf.zon"); + _ = f; +} + +// error +// imports=zon/neg_inf.zon +// +// neg_inf.zon:1:1: error: expected type 'comptime_float' +// tmp.zig:2:39: note: imported here diff --git a/test/cases/compile_errors/zon/inf.zon b/test/cases/compile_errors/zon/inf.zon new file mode 100644 index 000000000000..a28aa9a0ff05 --- /dev/null +++ b/test/cases/compile_errors/zon/inf.zon @@ -0,0 +1 @@ +inf \ No newline at end of file diff --git a/test/cases/compile_errors/zon/nan.zon b/test/cases/compile_errors/zon/nan.zon new file mode 100644 index 000000000000..0982929c57fa --- /dev/null +++ b/test/cases/compile_errors/zon/nan.zon @@ -0,0 +1 @@ +nan \ No newline at end of file diff --git a/test/cases/compile_errors/zon/neg_inf.zon b/test/cases/compile_errors/zon/neg_inf.zon new file mode 100644 index 000000000000..f9111418ae47 --- /dev/null +++ b/test/cases/compile_errors/zon/neg_inf.zon @@ -0,0 +1 @@ +-inf \ No newline at end of file From b58f169e90e8a2a11d80f1af148a558e2b4b0afb Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 24 Jan 2025 18:05:27 -0800 Subject: [PATCH 56/98] Resolves more feedback --- lib/std/zon/parse.zig | 46 +++++++++++++++------------------------ lib/std/zon/stringify.zig | 10 ++++----- src/Sema/LowerZon.zig | 29 ++++++++++++------------ 3 files changed, 38 insertions(+), 47 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 8324ea83bb37..77ce2539b19d 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -265,10 +265,10 @@ pub fn fromSlice( defer if (status == null) ast.deinit(gpa); if (status) |s| s.ast = ast; + // If there's no status, Zoir exists for the lifetime of this function. If there is a status, + // ownership is transferred to status. var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false }); defer if (status == null) zoir.deinit(gpa); - if (status) |s| s.zoir = zoir; - if (zoir.hasCompileErrors()) return error.ParseZon; if (status) |s| s.* = .{}; return fromZoir(T, gpa, ast, zoir, status, options); @@ -508,7 +508,7 @@ const Parser = struct { const ast_node = node.getAstNode(self.zoir); const pointer = @typeInfo(T).pointer; var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); - if (pointer.sentinel_ptr != null) size_hint += 1; + if (pointer.sentinel() != null) size_hint += 1; var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, size_hint); defer buf.deinit(self.gpa); @@ -524,13 +524,13 @@ const Parser = struct { if (pointer.child != u8 or pointer.size != .slice or !pointer.is_const or - (pointer.sentinel_ptr != null and @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* != 0) or + (pointer.sentinel() != null and pointer.sentinel() != 0) or pointer.alignment != 1) { return self.failExpectedContainer(T, node); } - if (pointer.sentinel_ptr != null) { + if (pointer.sentinel() != null) { return try buf.toOwnedSliceSentinel(self.gpa, 0); } else { return try buf.toOwnedSlice(self.gpa); @@ -552,23 +552,22 @@ const Parser = struct { } // Allocate the slice - const sentinel = if (pointer.sentinel_ptr) |s| @as(*const pointer.child, @ptrCast(s)).* else null; const slice = try self.gpa.allocWithOptions( pointer.child, nodes.len, pointer.alignment, - sentinel, + pointer.sentinel(), ); errdefer self.gpa.free(slice); // Parse the elements and return the slice - for (0..nodes.len) |i| { + for (slice, 0..) |*elem, 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))); + elem.* = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); } return slice; @@ -606,7 +605,7 @@ const Parser = struct { // Parse the elements and return the array var result: T = undefined; - for (0..result.len) |i| { + for (&result, 0..) |*elem, i| { // If we fail to parse this field, free all fields before it errdefer if (options.free_on_error) { for (result[0..i]) |item| { @@ -614,7 +613,7 @@ const Parser = struct { } }; - result[i] = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); + elem.* = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); } return result; } @@ -635,10 +634,10 @@ const Parser = struct { 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 }; + const field_indices = comptime b: { + var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; + for (&kvs_list, field_infos, 0..) |*kv, field, i| { + kv.* = .{ field.name, i }; } break :b std.StaticStringMap(usize).initComptime(kvs_list); }; @@ -965,7 +964,7 @@ const Parser = struct { }); } }, - else => @compileError("unreachable, should not be called for type " ++ @typeName(T)), + else => comptime unreachable, } } @@ -983,7 +982,7 @@ const Parser = struct { if (pointer.child == u8 and pointer.size == .slice and pointer.is_const and - (pointer.sentinel_ptr == null or @as(*const u8, @ptrCast(pointer.sentinel_ptr)).* == 0) and + (pointer.sentinel() == null or pointer.sentinel() == 0) and pointer.alignment == 1) { return self.failNode(node, "expected string"); @@ -993,7 +992,7 @@ const Parser = struct { }, else => {}, } - @compileError("unreachable, should not be called for type " ++ @typeName(T)); + comptime unreachable; } // Technically we could do this if we were willing to do a deep equal to verify @@ -1020,15 +1019,6 @@ const Parser = struct { }; 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; } @@ -1037,7 +1027,7 @@ fn intFromFloatExact(comptime T: type, value: anytype) ?T { return null; } - return @as(T, @intFromFloat(value)); + return @intFromFloat(value); } test "std.zon requiresAllocator" { diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index e4d94f5f9757..83fd8d28401b 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -30,7 +30,7 @@ pub const SerializeOptions = struct { /// 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. + /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. /// Otherwise they are serialized as string literals. emit_strings_as_containers: bool = false, /// If false, struct fields are not written if they are equal to their default value. Comparison @@ -43,7 +43,7 @@ pub const SerializeOptions = struct { /// It is asserted at comptime that `@TypeOf(val)` is not a recursive type. pub fn serialize( val: anytype, - comptime options: SerializeOptions, + options: SerializeOptions, writer: anytype, ) @TypeOf(writer).Error!void { var sz = serializer(writer, .{ @@ -61,7 +61,7 @@ pub fn serialize( /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. pub fn serializeMaxDepth( val: anytype, - comptime options: SerializeOptions, + options: SerializeOptions, writer: anytype, depth: usize, ) (@TypeOf(writer).Error || error{ExceededMaxDepth})!void { @@ -80,7 +80,7 @@ pub fn serializeMaxDepth( /// It is the caller's responsibility to ensure that `val` does not contain cycles. pub fn serializeArbitraryDepth( val: anytype, - comptime options: SerializeOptions, + options: SerializeOptions, writer: anytype, ) @TypeOf(writer).Error!void { var sz = serializer(writer, .{ @@ -949,7 +949,7 @@ pub fn serializer(writer: anytype, options: SerializerOptions) Serializer(@TypeO fn expectSerializeEqual( expected: []const u8, value: anytype, - comptime options: SerializeOptions, + options: SerializeOptions, ) !void { var buf = std.ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index f3c9f62ff1c9..f5028b84c1a9 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -396,7 +396,6 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; - const gpa = self.sema.gpa; switch (node.get(self.file.zoir.?)) { .enum_literal => |field_name| { const field_name_interned = try ip.getOrPutString( @@ -405,7 +404,7 @@ fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !Intern field_name.get(self.file.zoir.?), .no_embedded_nulls, ); - return ip.get(gpa, self.sema.pt.tid, .{ .enum_literal = field_name_interned }); + return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); }, else => return self.fail( .{ .node_abs = node.getAstNode(self.file.zoir.?) }, @@ -468,9 +467,9 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I } } - for (0..elems.len) |i| { - if (elems[i] == .none and i < field_defaults.len) { - elems[i] = field_defaults[i]; + for (elems, 0..) |*elem, i| { + if (elem.* == .none and i < field_defaults.len) { + elem.* = field_defaults[i]; } } @@ -557,9 +556,11 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. } } - for (0..field_values.len) |i| { - if (field_values[i] == .none and i < field_defaults.len) { - field_values[i] = field_defaults[i]; + if (field_defaults.len > 0) { + for (field_values, 0..) |*field_value, i| { + if (field_value.* == .none) { + field_value.* = field_defaults[i]; + } } } @@ -623,8 +624,8 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); - for (0..elem_nodes.len) |i| { - elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child)); + for (elems, 0..) |*elem, i| { + elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(ptr_info.child)); } if (ptr_info.sentinel != .none) { @@ -642,7 +643,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool .storage = .{ .elems = elems }, } }); - const many_item_ptr_type = try ip.get(gpa, self.sema.pt.tid, .{ .ptr_type = .{ + const many_item_ptr_type = try self.sema.pt.intern(.{ .ptr_type = .{ .child = ptr_info.child, .sentinel = ptr_info.sentinel, .flags = b: { @@ -653,7 +654,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool .packed_offset = ptr_info.packed_offset, } }); - const many_item_ptr = try ip.get(gpa, self.sema.pt.tid, .{ + const many_item_ptr = try self.sema.pt.intern(.{ .ptr = .{ .ty = many_item_ptr_type, .base_addr = .{ @@ -666,9 +667,9 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool }, }); - const len = (try self.sema.pt.intValue(Type.usize, elems.len)).toIntern(); + const len = (try self.sema.pt.intValue(.usize, elems.len)).toIntern(); - return ip.get(gpa, self.sema.pt.tid, .{ .slice = .{ + return self.sema.pt.intern(.{ .slice = .{ .ty = res_ty.toIntern(), .ptr = many_item_ptr, .len = len, From d097b95f812016bcdd771b0e7e2bcc1836449984 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 27 Jan 2025 16:24:10 -0800 Subject: [PATCH 57/98] Replaces explicit slice serialization with explicit tuple serialization --- lib/std/zon/stringify.zig | 201 ++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 116 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 83fd8d28401b..081506b8a61c 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -243,7 +243,7 @@ pub const ValueOptions = struct { 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. + /// If true, wrap every field. If false do not. wrap: bool, /// Automatically decide whether to wrap or not based on the number of fields. Following /// the standard rule of thumb, containers with more than two fields are wrapped. @@ -260,7 +260,7 @@ pub const SerializeContainerOptions = struct { /// Lower level control over serialization, you can create a new instance with `serializer`. /// -/// Useful when you want control over which fields/items are serialized, how they're represented, +/// Useful when you want control over which fields are serialized, how they're represented, /// or want to write a ZON object that does not exist in memory. /// /// You can serialize values with `value`. To serialize recursive types, the following are provided: @@ -271,16 +271,15 @@ pub const SerializeContainerOptions = struct { /// * `int` /// * `float` /// * `utf8Codepoint` -/// * `slice` -/// * `sliceMaxDepth` -/// * `sliceArbitraryDepth` +/// * `tuple` +/// * `tupleMaxDepth` +/// * `tupleArbitraryDepth` /// * `string` /// * `multilineString` /// /// For manual serialization of containers, see: /// * `startStruct` /// * `startTuple` -/// * `startSlice` /// /// # Example /// ```zig @@ -380,7 +379,7 @@ pub fn Serializer(Writer: type) type { if (child_type == u8 and !options.emit_strings_as_containers) { try self.string(val); } else { - try self.sliceImpl(val, options); + try self.tupleImpl(val, options); } }, .array => { @@ -519,44 +518,56 @@ pub fn Serializer(Writer: type) type { try std.fmt.format(self.writer, "'{'}'", .{std.zig.fmtEscapes(str)}); } - /// Like `value`, but always serializes `val` as a slice. + /// Like `value`, but always serializes `val` as a tuple. /// - /// Will fail at comptime if `val` is not an array or slice. - pub fn slice(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. + pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { comptimeAssertNoRecursion(@TypeOf(val)); - try self.sliceArbitraryDepth(val, options); + try self.tupleArbitraryDepth(val, options); } - /// Like `value`, but recursive types are allowed. + /// Like `tuple`, but recursive types are allowed. /// /// Returns `error.ExceededMaxDepth` if `depth` is exceeded. - pub fn sliceMaxDepth( + pub fn tupleMaxDepth( self: *Self, val: anytype, options: ValueOptions, depth: usize, ) (Writer.Error || error{ExceededMaxDepth})!void { try checkValueDepth(val, depth); - try self.sliceArbitraryDepth(val, options); + try self.tupleArbitraryDepth(val, options); } - /// Like `value`, but recursive types are allowed. + /// Like `tuple`, but recursive types are allowed. /// /// It is the caller's responsibility to ensure that `val` does not contain cycles. - pub fn sliceArbitraryDepth( + pub fn tupleArbitraryDepth( self: *Self, val: anytype, options: ValueOptions, ) Writer.Error!void { - try self.sliceImpl(val, options); + try self.tupleImpl(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); + fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + switch (@typeInfo(@TypeOf(val))) { + .@"struct" => { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + inline for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + .pointer, .array => { + var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); + for (val) |item_val| { + try container.fieldArbitraryDepth(item_val, options); + } + try container.finish(); + }, + else => comptime unreachable, } - try container.finish(); } /// Like `value`, but always serializes `val` as a string. @@ -633,14 +644,6 @@ pub fn Serializer(Writer: type) type { return Tuple.start(self, options); } - /// Creates a `Slice` for writing ZON slices item by item. - pub fn startSlice( - self: *Self, - options: SerializeContainerOptions, - ) Writer.Error!Slice { - return Slice.start(self, options); - } - fn indent(self: *Self) Writer.Error!void { if (self.options.whitespace) { try self.writer.writeByteNTimes(' ', 4 * self.indent_level); @@ -779,61 +782,6 @@ pub fn Serializer(Writer: type) type { } }; - /// Writes ZON slices field by field. - pub const Slice = struct { - container: Container, - - fn start(parent: *Self, options: SerializeContainerOptions) Writer.Error!Slice { - try parent.writer.writeByte('&'); - return .{ - .container = try Container.start(parent, .anon, options), - }; - } - - /// Finishes serializing the slice. - /// - /// Prints a trailing comma as configured when appropriate, and the closing bracket. - pub fn finish(self: *Slice) Writer.Error!void { - try self.container.finish(); - self.* = undefined; - } - - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `value`. - pub fn item( - self: *Slice, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.container.field(null, val, options); - } - - /// Serialize an item. Equivalent to calling `itemPrefix` followed by `valueMaxDepth`. - pub fn itemMaxDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - depth: usize, - ) (Writer.Error || error{ExceededMaxDepth})!void { - try self.container.fieldMaxDepth(null, val, options, depth); - } - - /// Serialize an item. Equivalent to calling `itemPrefix` followed by - /// `valueArbitraryDepth`. - pub fn itemArbitraryDepth( - self: *Slice, - val: anytype, - options: ValueOptions, - ) Writer.Error!void { - try self.container.fieldArbitraryDepth(null, val, options); - } - - /// Print a field prefix. This prints any necessary commas, and whitespace as - /// configured. Useful if you want to serialize the item value yourself. - pub fn itemPrefix(self: *Slice) Writer.Error!void { - try self.container.fieldPrefix(null); - } - }; - const Container = struct { const FieldStyle = enum { named, anon }; @@ -967,8 +915,8 @@ test "std.zon stringify whitespace, high level API" { try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{}); try expectSerializeEqual(".{1}", @as([1]u32, .{1}), .{ .whitespace = false }); - try expectSerializeEqual("&.{1}", @as([]const u32, &.{1}), .{}); - try expectSerializeEqual("&.{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); + try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{}); + try expectSerializeEqual(".{1}", @as([]const u32, &.{1}), .{ .whitespace = false }); try expectSerializeEqual(".{ .x = 1 }", .{ .x = 1 }, .{}); try expectSerializeEqual(".{.x=1}", .{ .x = 1 }, .{ .whitespace = false }); @@ -979,8 +927,8 @@ test "std.zon stringify whitespace, high level API" { try expectSerializeEqual(".{ 1, 2 }", @as([2]u32, .{ 1, 2 }), .{}); try expectSerializeEqual(".{1,2}", @as([2]u32, .{ 1, 2 }), .{ .whitespace = false }); - try expectSerializeEqual("&.{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{}); - try expectSerializeEqual("&.{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false }); + try expectSerializeEqual(".{ 1, 2 }", @as([]const u32, &.{ 1, 2 }), .{}); + try expectSerializeEqual(".{1,2}", @as([]const u32, &.{ 1, 2 }), .{ .whitespace = false }); try expectSerializeEqual(".{ .x = 1, .y = 2 }", .{ .x = 1, .y = 2 }, .{}); try expectSerializeEqual(".{.x=1,.y=2}", .{ .x = 1, .y = 2 }, .{ .whitespace = false }); @@ -1004,14 +952,14 @@ test "std.zon stringify whitespace, high level API" { try expectSerializeEqual(".{1,2,3}", @as([3]u32, .{ 1, 2, 3 }), .{ .whitespace = false }); try expectSerializeEqual( - \\&.{ + \\.{ \\ 1, \\ 2, \\ 3, \\} , @as([]const u32, &.{ 1, 2, 3 }), .{}); try expectSerializeEqual( - "&.{1,2,3}", + ".{1,2,3}", @as([]const u32, &.{ 1, 2, 3 }), .{ .whitespace = false }, ); @@ -1501,9 +1449,9 @@ test "std.zon stringify strings" { try std.testing.expectEqualStrings("\"abc\\xe2\\x9a\\xa1\\n\"", buf.items); buf.clearRetainingCapacity(); - try sz.slice("abcâš¡\n", .{}); + try sz.tuple("abcâš¡\n", .{}); try std.testing.expectEqualStrings( - \\&.{ + \\.{ \\ 97, \\ 98, \\ 99, @@ -1521,7 +1469,7 @@ test "std.zon stringify strings" { try sz.value("abcâš¡\n", .{ .emit_strings_as_containers = true }); try std.testing.expectEqualStrings( - \\&.{ + \\.{ \\ 97, \\ 98, \\ 99, @@ -1540,7 +1488,7 @@ test "std.zon stringify strings" { try sz.value(.{ .str = "abc" }, .{ .emit_strings_as_containers = true }); try std.testing.expectEqualStrings( - \\.{ .str = &.{ + \\.{ .str = .{ \\ 97, \\ 98, \\ 99, @@ -1808,7 +1756,7 @@ test "std.zon depth limits" { { const maybe_recurse = Recurse{ .r = &.{} }; try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 2); - try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); + try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1816,7 +1764,7 @@ test "std.zon depth limits" { { const maybe_recurse = Recurse{ .r = &.{} }; try serializeArbitraryDepth(maybe_recurse, .{}, buf.writer()); - try std.testing.expectEqualStrings(".{ .r = &.{} }", buf.items); + try std.testing.expectEqualStrings(".{ .r = .{} }", buf.items); buf.clearRetainingCapacity(); } @@ -1848,13 +1796,13 @@ test "std.zon depth limits" { try std.testing.expectError( error.ExceededMaxDepth, - sz.sliceMaxDepth(maybe_recurse, .{}, 2), + sz.tupleMaxDepth(maybe_recurse, .{}, 2), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); - try sz.sliceArbitraryDepth(maybe_recurse, .{}); - try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + try sz.tupleArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); buf.clearRetainingCapacity(); } @@ -1864,17 +1812,17 @@ test "std.zon depth limits" { const maybe_recurse: []const Recurse = &temp; try serializeMaxDepth(maybe_recurse, .{}, buf.writer(), 3); - try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); buf.clearRetainingCapacity(); var sz = serializer(buf.writer(), .{}); - try sz.sliceMaxDepth(maybe_recurse, .{}, 3); - try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + try sz.tupleMaxDepth(maybe_recurse, .{}, 3); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); buf.clearRetainingCapacity(); - try sz.sliceArbitraryDepth(maybe_recurse, .{}); - try std.testing.expectEqualStrings("&.{.{ .r = &.{} }}", buf.items); + try sz.tupleArbitraryDepth(maybe_recurse, .{}); + try std.testing.expectEqualStrings(".{.{ .r = .{} }}", buf.items); buf.clearRetainingCapacity(); } @@ -1894,7 +1842,7 @@ test "std.zon depth limits" { var sz = serializer(buf.writer(), .{}); try std.testing.expectError( error.ExceededMaxDepth, - sz.sliceMaxDepth(maybe_recurse, .{}, 128), + sz.tupleMaxDepth(maybe_recurse, .{}, 128), ); try std.testing.expectEqualStrings("", buf.items); buf.clearRetainingCapacity(); @@ -1925,26 +1873,26 @@ test "std.zon depth limits" { try t.fieldArbitraryDepth(maybe_recurse, .{}); try t.finish(); - var a = try sz.startSlice(.{}); - try std.testing.expectError(error.ExceededMaxDepth, a.itemMaxDepth(1, .{}, 0)); - try a.itemMaxDepth(8, .{}, 1); - try a.item(9, .{}); - try a.itemArbitraryDepth(maybe_recurse, .{}); + var a = try sz.startTuple(.{}); + try std.testing.expectError(error.ExceededMaxDepth, a.fieldMaxDepth(1, .{}, 0)); + try a.fieldMaxDepth(8, .{}, 1); + try a.field(9, .{}); + try a.fieldArbitraryDepth(maybe_recurse, .{}); try a.finish(); try std.testing.expectEqualStrings( - \\23&.{}.{ + \\23.{}.{ \\ .b = 4, \\ .c = 5, - \\ .d = &.{}, + \\ .d = .{}, \\}.{ \\ 6, \\ 7, - \\ &.{}, - \\}&.{ + \\ .{}, + \\}.{ \\ 8, \\ 9, - \\ &.{}, + \\ .{}, \\} , buf.items); } @@ -2086,3 +2034,24 @@ test "std.zon stringify ident" { .@"1" = Enum.@"foo bar", }, .{}); } + +test "std.zon stringify as tuple" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Tuples + try sz.tuple(.{ 1, 2 }, .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); + + // Slice + try sz.tuple(@as([]const u8, &.{ 1, 2 }), .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); + + // Array + try sz.tuple([2]u8{ 1, 2 }, .{}); + try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); + buf.clearRetainingCapacity(); +} From dbd686d309dae329023d97264a698bfa5122850f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 27 Jan 2025 16:45:14 -0800 Subject: [PATCH 58/98] Resovles more feedback --- lib/std/zon/parse.zig | 8 +++--- lib/std/zon/stringify.zig | 57 ++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 77ce2539b19d..8a8067fa17df 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -644,7 +644,7 @@ const Parser = struct { // Parse the struct var result: T = undefined; - var field_found: [field_infos.len]bool = .{false} ** field_infos.len; + var field_found: [field_infos.len]bool = @splat(false); // If we fail partway through, free all already initialized fields var initialized: usize = 0; @@ -1014,7 +1014,7 @@ const Parser = struct { 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", .{}); + return self.failTokenFmt(token, 0, "cannot initialize comptime field", .{}); } }; @@ -1374,7 +1374,7 @@ test "std.zon structs" { 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 + \\1:18: error: cannot initialize comptime field \\ , "{}", .{status}); } @@ -1567,7 +1567,7 @@ test "std.zon tuples" { 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 + \\1:9: error: cannot initialize comptime field \\ , "{}", .{status}); } diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 081506b8a61c..87649d62f4a2 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -24,8 +24,7 @@ const std = @import("std"); /// Options for `serialize`. pub const SerializeOptions = struct { - /// If false, all whitespace is emitted. Otherwise, whitespace is emitted in the standard Zig - /// style when possible. + /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. 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. @@ -93,45 +92,36 @@ pub fn serializeArbitraryDepth( }); } -const RecursiveTypeBuffer = [32]type; - fn typeIsRecursive(comptime T: type) bool { - comptime var buf: RecursiveTypeBuffer = undefined; - return comptime typeIsRecursiveImpl(T, buf[0..0]); + return comptime typeIsRecursiveImpl(T, &.{}); } -fn typeIsRecursiveImpl(comptime T: type, comptime visited_arg: []type) bool { - comptime var visited = visited_arg; - +fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []type) bool { // Check if we've already seen this type - inline for (visited) |found| { + inline for (prev_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; + // Create a copy of visited with this type added + comptime var visited = prev_visited[0..prev_visited.len].* ++ .{T}; // Recurse switch (@typeInfo(T)) { - .pointer => |pointer| return typeIsRecursiveImpl(pointer.child, visited), - .array => |array| return typeIsRecursiveImpl(array.child, visited), + .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)) { + if (typeIsRecursiveImpl(field.type, &visited)) { return true; } }, .@"union" => |@"union"| inline for (@"union".fields) |field| { - if (typeIsRecursiveImpl(field.type, visited)) { + if (typeIsRecursiveImpl(field.type, &visited)) { return true; } }, - .optional => |optional| return typeIsRecursiveImpl(optional.child, visited), + .optional => |optional| return typeIsRecursiveImpl(optional.child, &visited), else => {}, } return false; @@ -405,7 +395,7 @@ pub fn Serializer(Writer: type) type { 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; + var skipped: [@"struct".fields.len]bool = @splat(false); inline for (@"struct".fields, &skipped) |field_info, *skip| { if (field_info.default_value_ptr) |default_field_value_opaque| { const field_value = @field(val, field_info.name); @@ -467,15 +457,16 @@ pub fn Serializer(Writer: type) type { /// 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)) { + .float => if (std.math.isNan(val)) { return self.writer.writeAll("nan"); - } else if (@as(f128, val) == std.math.inf(f128)) { + } else if (std.math.isPositiveInf(val)) { return self.writer.writeAll("inf"); - } else if (@as(f128, val) == -std.math.inf(f128)) { + } else if (std.math.isNegativeInf(val)) { return self.writer.writeAll("-inf"); } else { try std.fmt.format(self.writer, "{d}", .{val}); }, + .comptime_float => try std.fmt.format(self.writer, "{d}", .{val}), else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), } } @@ -2055,3 +2046,19 @@ test "std.zon stringify as tuple" { try std.testing.expectEqualStrings(".{ 1, 2 }", buf.items); buf.clearRetainingCapacity(); } + +test "std.zon stringify as float" { + var buf = std.ArrayList(u8).init(std.testing.allocator); + defer buf.deinit(); + var sz = serializer(buf.writer(), .{}); + + // Comptime float + try sz.float(2.5); + try std.testing.expectEqualStrings("2.5", buf.items); + buf.clearRetainingCapacity(); + + // Sized float + try sz.float(@as(f32, 2.5)); + try std.testing.expectEqualStrings("2.5", buf.items); + buf.clearRetainingCapacity(); +} From fca21f34e3fb9642ca0c1a836776359e53aeaf25 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 27 Jan 2025 18:46:02 -0800 Subject: [PATCH 59/98] Better Unicode codepoint handling --- lib/std/zon/stringify.zig | 157 +++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 55 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 87649d62f4a2..29e10ea87904 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -26,9 +26,8 @@ const std = @import("std"); pub const SerializeOptions = struct { /// If false, whitespace is omitted. Otherwise whitespace is emitted in standard Zig style. 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, + /// Determines when to emit Unicode code point literals as opposed to integer literals. + emit_codepoint_literals: EmitCodepointLiterals = .never, /// If true, slices of `u8`s, and pointers to arrays of `u8` are serialized as containers. /// Otherwise they are serialized as string literals. emit_strings_as_containers: bool = false, @@ -49,7 +48,7 @@ pub fn serialize( .whitespace = options.whitespace, }); try sz.value(val, .{ - .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_codepoint_literals = options.emit_codepoint_literals, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, }); @@ -68,7 +67,7 @@ pub fn serializeMaxDepth( .whitespace = options.whitespace, }); try sz.valueMaxDepth(val, .{ - .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_codepoint_literals = options.emit_codepoint_literals, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, }, depth); @@ -86,7 +85,7 @@ pub fn serializeArbitraryDepth( .whitespace = options.whitespace, }); try sz.valueArbitraryDepth(val, .{ - .emit_utf8_codepoints = options.emit_utf8_codepoints, + .emit_codepoint_literals = options.emit_codepoint_literals, .emit_strings_as_containers = options.emit_strings_as_containers, .emit_default_optional_fields = options.emit_default_optional_fields, }); @@ -222,9 +221,52 @@ pub const SerializerOptions = struct { whitespace: bool = true, }; +/// Determines when to emit Unicode code point literals as opposed to integer literals. +pub const EmitCodepointLiterals = enum { + /// Never emit Unicode code point literals. + never, + /// Emit Unicode code point literals for any `u8` in the printable ASCII range. + printable_ascii, + /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer + /// whose value is a valid non-surrogate code point. + always, + + /// If the value should be emitted as a Unicode codepoint, return it as a u21. + fn emitAsCodepoint(self: @This(), val: anytype) ?u21 { + // Rule out incompatible integer types + switch (@typeInfo(@TypeOf(val))) { + .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) { + return null; + }, + .comptime_int => {}, + else => comptime unreachable, + } + + // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted + // to a u21 if it should. + switch (self) { + .always => { + const c = std.math.cast(u21, val) orelse return null; + if (!std.unicode.utf8ValidCodepoint(c)) return null; + return c; + }, + .printable_ascii => { + const c = std.math.cast(u8, val) orelse return null; + if (!std.ascii.isPrint(c)) return null; + return c; + }, + .never => { + return null; + }, + } + } +}; + /// Options for serialization of an individual value. +/// +/// See `SerializeOptions` for more information on these options. pub const ValueOptions = struct { - emit_utf8_codepoints: bool = false, + emit_codepoint_literals: EmitCodepointLiterals = .never, emit_strings_as_containers: bool = false, emit_default_optional_fields: bool = true, }; @@ -260,7 +302,7 @@ pub const SerializeContainerOptions = struct { /// You can also serialize values using specific notations: /// * `int` /// * `float` -/// * `utf8Codepoint` +/// * `codePoint` /// * `tuple` /// * `tupleMaxDepth` /// * `tupleArbitraryDepth` @@ -321,23 +363,8 @@ pub fn Serializer(Writer: type) type { 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) { + .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { + self.codePoint(c) catch |err| switch (err) { error.InvalidCodepoint => unreachable, // Already validated else => |e| return e, }; @@ -496,10 +523,10 @@ pub fn Serializer(Writer: type) type { } } - /// Serialize `val` as a UTF8 codepoint. + /// Serialize `val` as a Unicode codepoint. /// - /// Returns `error.InvalidCodepoint` if `val` is not a valid UTF8 codepoint. - pub fn utf8Codepoint( + /// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint. + pub fn codePoint( self: *Self, val: u21, ) (Writer.Error || error{InvalidCodepoint})!void { @@ -1345,87 +1372,107 @@ test "std.zon stringify utf8 codepoints" { defer buf.deinit(); var sz = serializer(buf.writer(), .{}); - // Minimal case - try sz.utf8Codepoint('a'); + // Printable ASCII + try sz.int('a'); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.codePoint('a'); try std.testing.expectEqualStrings("'a'", buf.items); buf.clearRetainingCapacity(); - try sz.int('a'); - try std.testing.expectEqualStrings("97", buf.items); + try sz.value('a', .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("'a'", buf.items); buf.clearRetainingCapacity(); - try sz.value('a', .{ .emit_utf8_codepoints = true }); + try sz.value('a', .{ .emit_codepoint_literals = .printable_ascii }); try std.testing.expectEqualStrings("'a'", buf.items); buf.clearRetainingCapacity(); - try sz.value('a', .{ .emit_utf8_codepoints = false }); + try sz.value('a', .{ .emit_codepoint_literals = .never }); try std.testing.expectEqualStrings("97", buf.items); buf.clearRetainingCapacity(); // Short escaped codepoint - try sz.utf8Codepoint('\n'); - try std.testing.expectEqualStrings("'\\n'", buf.items); - buf.clearRetainingCapacity(); - try sz.int('\n'); try std.testing.expectEqualStrings("10", buf.items); buf.clearRetainingCapacity(); - try sz.value('\n', .{ .emit_utf8_codepoints = true }); + try sz.codePoint('\n'); + try std.testing.expectEqualStrings("'\\n'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('\n', .{ .emit_codepoint_literals = .always }); try std.testing.expectEqualStrings("'\\n'", buf.items); buf.clearRetainingCapacity(); - try sz.value('\n', .{ .emit_utf8_codepoints = false }); + try sz.value('\n', .{ .emit_codepoint_literals = .printable_ascii }); try std.testing.expectEqualStrings("10", buf.items); buf.clearRetainingCapacity(); - // Large codepoint - try sz.utf8Codepoint('âš¡'); - try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + try sz.value('\n', .{ .emit_codepoint_literals = .never }); + try std.testing.expectEqualStrings("10", buf.items); buf.clearRetainingCapacity(); + // Large codepoint try sz.int('âš¡'); try std.testing.expectEqualStrings("9889", buf.items); buf.clearRetainingCapacity(); - try sz.value('âš¡', .{ .emit_utf8_codepoints = true }); + try sz.codePoint('âš¡'); try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); buf.clearRetainingCapacity(); - try sz.value('âš¡', .{ .emit_utf8_codepoints = false }); + try sz.value('âš¡', .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("'\\xe2\\x9a\\xa1'", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("9889", buf.items); + buf.clearRetainingCapacity(); + + try sz.value('âš¡', .{ .emit_codepoint_literals = .never }); try std.testing.expectEqualStrings("9889", buf.items); buf.clearRetainingCapacity(); // Invalid codepoint - try std.testing.expectError(error.InvalidCodepoint, sz.utf8Codepoint(0x110000 + 1)); + try std.testing.expectError(error.InvalidCodepoint, sz.codePoint(0x110000 + 1)); try sz.int(0x110000 + 1); try std.testing.expectEqualStrings("1114113", buf.items); buf.clearRetainingCapacity(); - try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = true }); + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .always }); try std.testing.expectEqualStrings("1114113", buf.items); buf.clearRetainingCapacity(); - try sz.value(0x110000 + 1, .{ .emit_utf8_codepoints = false }); + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .printable_ascii }); + try std.testing.expectEqualStrings("1114113", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(0x110000 + 1, .{ .emit_codepoint_literals = .never }); try std.testing.expectEqualStrings("1114113", buf.items); buf.clearRetainingCapacity(); // Valid codepoint, not a codepoint type - try sz.value(@as(u22, 'a'), .{ .emit_utf8_codepoints = true }); + try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .always }); + try std.testing.expectEqualStrings("97", buf.items); + buf.clearRetainingCapacity(); + + try sz.value(@as(u22, 'a'), .{ .emit_codepoint_literals = .printable_ascii }); try std.testing.expectEqualStrings("97", buf.items); buf.clearRetainingCapacity(); - try sz.value(@as(i32, 'a'), .{ .emit_utf8_codepoints = false }); + try sz.value(@as(i32, 'a'), .{ .emit_codepoint_literals = .never }); try std.testing.expectEqualStrings("97", buf.items); buf.clearRetainingCapacity(); // Make sure value options are passed to children - try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = true }); + try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .always }); try std.testing.expectEqualStrings(".{ .c = '\\xe2\\x9a\\xa1' }", buf.items); buf.clearRetainingCapacity(); - try sz.value(.{ .c = 'âš¡' }, .{ .emit_utf8_codepoints = false }); + try sz.value(.{ .c = 'âš¡' }, .{ .emit_codepoint_literals = .never }); try std.testing.expectEqualStrings(".{ .c = 9889 }", buf.items); buf.clearRetainingCapacity(); } @@ -1639,7 +1686,7 @@ test "std.zon stringify skip default fields" { 'd', }, }, - .{ .emit_utf8_codepoints = true }, + .{ .emit_codepoint_literals = .always }, ); // Top level defaults @@ -1666,7 +1713,7 @@ test "std.zon stringify skip default fields" { }, .{ .emit_default_optional_fields = false, - .emit_utf8_codepoints = true, + .emit_codepoint_literals = .always, }, ); @@ -1699,7 +1746,7 @@ test "std.zon stringify skip default fields" { }, .{ .emit_default_optional_fields = false, - .emit_utf8_codepoints = true, + .emit_codepoint_literals = .always, }, ); From d37d19f029fb17b11ce937e362818a04d06f42bf Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 27 Jan 2025 19:02:58 -0800 Subject: [PATCH 60/98] Makes default field handling more readable --- lib/std/zon/stringify.zig | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 29e10ea87904..542d3ecea640 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -418,18 +418,16 @@ pub fn Serializer(Writer: type) type { 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 }; + const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: { + break :b .{ @"struct".fields.len, @splat(false) }; } else b: { var fields = @"struct".fields.len; var skipped: [@"struct".fields.len]bool = @splat(false); inline for (@"struct".fields, &skipped) |field_info, *skip| { - if (field_info.default_value_ptr) |default_field_value_opaque| { + if (field_info.default_value_ptr) |ptr| { + const default: *const field_info.type = @ptrCast(@alignCast(ptr)); 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.*)) { + if (std.meta.eql(field_value, default.*)) { skip.* = true; fields -= 1; } From c7233bd7688d85660b1298d907d5fc120e257282 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Mon, 27 Jan 2025 19:17:02 -0800 Subject: [PATCH 61/98] Checks sentinel when deciding to serialize as string or not --- lib/std/zon/stringify.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 542d3ecea640..bb0bc17d0b5e 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -393,7 +393,11 @@ pub fn Serializer(Writer: type) type { @typeName(@TypeOf(val)) ++ ": cannot stringify pointer to this type", ) else pointer.child, }; - if (child_type == u8 and !options.emit_strings_as_containers) { + const sentinel = pointer.sentinel(); + if (child_type == u8 and + (sentinel == null or sentinel == 0) and + !options.emit_strings_as_containers) + { try self.string(val); } else { try self.tupleImpl(val, options); From 9b24417dfab5450936a8d65827bbb61013705d95 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 28 Jan 2025 15:55:52 -0800 Subject: [PATCH 62/98] Fixes errors on tuples vs arrays --- lib/std/zon/parse.zig | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 8a8067fa17df..42eb325750f8 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -588,19 +588,18 @@ const Parser = struct { const array_info = @typeInfo(T).array; // Check if the size matches - if (nodes.len > array_info.len) { + if (nodes.len < array_info.len) { + return self.failNodeFmt( + node, + "expected {} array elements; found {}", + .{ array_info.len, nodes.len }, + ); + } else if (nodes.len > array_info.len) { return self.failNodeFmt( nodes.at(array_info.len), - "index {} outside of tuple length {}", + "index {} outside of array of 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 @@ -1680,7 +1679,11 @@ test "std.zon arrays and slices" { error.ParseZon, fromSlice([0]u8, gpa, ".{'a', 'b', 'c'}", &status, .{}), ); - try std.testing.expectFmt("1:3: error: index 0 outside of tuple length 0\n", "{}", .{status}); + try std.testing.expectFmt( + "1:3: error: index 0 outside of array of length 0\n", + "{}", + .{status}, + ); } // Expect 1 find 2 @@ -1691,7 +1694,11 @@ test "std.zon arrays and slices" { error.ParseZon, fromSlice([1]u8, gpa, ".{'a', 'b'}", &status, .{}), ); - try std.testing.expectFmt("1:8: error: index 1 outside of tuple length 1\n", "{}", .{status}); + try std.testing.expectFmt( + "1:8: error: index 1 outside of array of length 1\n", + "{}", + .{status}, + ); } // Expect 2 find 1 @@ -1703,7 +1710,7 @@ test "std.zon arrays and slices" { fromSlice([2]u8, gpa, ".{'a'}", &status, .{}), ); try std.testing.expectFmt( - "1:2: error: missing tuple field with index 1\n", + "1:2: error: expected 2 array elements; found 1\n", "{}", .{status}, ); @@ -1718,7 +1725,7 @@ test "std.zon arrays and slices" { fromSlice([3]u8, gpa, ".{}", &status, .{}), ); try std.testing.expectFmt( - "1:2: error: missing tuple field with index 0\n", + "1:2: error: expected 3 array elements; found 0\n", "{}", .{status}, ); From ecb0574cb611917cb1991197afaf7913ff730bd8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 28 Jan 2025 16:22:35 -0800 Subject: [PATCH 63/98] Uses std.zig.fmtId --- lib/std/zon/stringify.zig | 46 ++++++++++----------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index bb0bc17d0b5e..6465369ec29d 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -373,12 +373,8 @@ pub fn Serializer(Writer: type) type { }, .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_literal => try self.ident(@tagName(val)), .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { - try self.writer.writeByte('.'); try self.ident(@tagName(val)); } else { @compileError( @@ -500,29 +496,11 @@ pub fn Serializer(Writer: type) type { } } - 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. + /// Serialize `name` as an identifier prefixed with `.`. /// /// 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); - } + try self.writer.print(".{p_}", .{std.zig.fmtId(name)}); } /// Serialize `val` as a Unicode codepoint. @@ -854,7 +832,6 @@ pub fn Serializer(Writer: type) type { } 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('='); @@ -2034,36 +2011,37 @@ test "std.zon stringify ident" { defer buf.deinit(); var sz = serializer(buf.writer(), .{}); + try expectSerializeEqual(".{ .a = 0 }", .{ .a = 0 }, .{}); try sz.ident("a"); - try std.testing.expectEqualStrings("a", buf.items); + try std.testing.expectEqualStrings(".a", buf.items); buf.clearRetainingCapacity(); try sz.ident("foo_1"); - try std.testing.expectEqualStrings("foo_1", buf.items); + try std.testing.expectEqualStrings(".foo_1", buf.items); buf.clearRetainingCapacity(); try sz.ident("_foo_1"); - try std.testing.expectEqualStrings("_foo_1", buf.items); + try std.testing.expectEqualStrings("._foo_1", buf.items); buf.clearRetainingCapacity(); try sz.ident("foo bar"); - try std.testing.expectEqualStrings("@\"foo bar\"", buf.items); + try std.testing.expectEqualStrings(".@\"foo bar\"", buf.items); buf.clearRetainingCapacity(); try sz.ident("1foo"); - try std.testing.expectEqualStrings("@\"1foo\"", buf.items); + try std.testing.expectEqualStrings(".@\"1foo\"", buf.items); buf.clearRetainingCapacity(); try sz.ident("var"); - try std.testing.expectEqualStrings("@\"var\"", buf.items); + try std.testing.expectEqualStrings(".@\"var\"", buf.items); buf.clearRetainingCapacity(); try sz.ident("true"); - try std.testing.expectEqualStrings("true", buf.items); + try std.testing.expectEqualStrings(".true", buf.items); buf.clearRetainingCapacity(); try sz.ident("_"); - try std.testing.expectEqualStrings("_", buf.items); + try std.testing.expectEqualStrings("._", buf.items); buf.clearRetainingCapacity(); const Enum = enum { From 8242604035ec3dc0bd0acb8dc2232f74e916ad64 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 28 Jan 2025 17:39:17 -0800 Subject: [PATCH 64/98] Moves verbose error content into note, improves dup field error --- lib/std/zon/parse.zig | 156 +++++++++++++++++++++++++++++++----------- 1 file changed, 117 insertions(+), 39 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 42eb325750f8..7121469c6482 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -38,6 +38,7 @@ pub const Error = union(enum) { pub const Note = union(enum) { zoir: Zoir.CompileError.Note, + type_check: TypeCheckFailure.Note, pub const Iterator = struct { index: usize = 0, @@ -53,7 +54,12 @@ pub const Error = union(enum) { self.index += 1; return .{ .zoir = note }; }, - .type_check => return null, + .type_check => |err| { + if (self.index >= err.getNoteCount()) return null; + const note = err.getNote(self.index); + self.index += 1; + return .{ .type_check = note }; + }, } } }; @@ -76,16 +82,15 @@ pub const Error = union(enum) { pub fn fmtMessage(self: Note, status: *const Status) std.fmt.Formatter(Note.formatMessage) { return .{ .data = switch (self) { .zoir => |note| note.msg.get(status.zoir.?), + .type_check => |note| note.msg, } }; } pub fn getLocation(self: Note, status: *const Status) Ast.Location { + const ast = status.ast.?; switch (self) { - .zoir => |note| return zoirErrorLocation( - status.ast.?, - note.token, - note.node_or_offset, - ), + .zoir => |note| return zoirErrorLocation(ast, note.token, note.node_or_offset), + .type_check => |note| return ast.tokenLocation(note.offset, note.token), } } }; @@ -116,15 +121,35 @@ pub const Error = union(enum) { }; const TypeCheckFailure = struct { + const Note = struct { + token: Ast.TokenIndex, + offset: u32, + msg: []const u8, + owned: bool, + + fn deinit(self: @This(), gpa: Allocator) void { + if (self.owned) gpa.free(self.msg); + } + }; + message: []const u8, owned: bool, token: Ast.TokenIndex, offset: u32, + note: ?@This().Note, fn deinit(self: @This(), gpa: Allocator) void { - if (self.owned) { - gpa.free(self.message); - } + if (self.note) |note| note.deinit(gpa); + if (self.owned) gpa.free(self.message); + } + + fn getNoteCount(self: @This()) usize { + return @intFromBool(self.note != null); + } + + fn getNote(self: @This(), index: usize) @This().Note { + assert(index == 0); + return self.note.?; } }; @@ -469,7 +494,7 @@ const Parser = struct { node: Zoir.Node.Index, ) !T { switch (node.get(self.zoir)) { - .enum_literal => |string| { + .enum_literal => |field_name| { // Create a comptime string map for the enum fields const enum_fields = @typeInfo(T).@"enum".fields; comptime var kvs_list: [enum_fields.len]struct { []const u8, T } = undefined; @@ -479,8 +504,9 @@ const Parser = struct { 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); + const field_name_str = field_name.get(self.zoir); + return enum_tags.get(field_name_str) orelse + self.failUnexpected(T, "enum literal", node, null, field_name_str); }, else => return self.failNode(node, "expected enum literal"), } @@ -661,12 +687,12 @@ const Parser = struct { // Fill in the fields we found for (0..fields.names.len) |i| { + const name = fields.names[i].get(self.zoir); 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); + return self.failUnexpected(T, "field", node, i, name); }; }; @@ -678,7 +704,7 @@ const Parser = struct { 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", .{}); + return self.failTokenFmt(token, 0, "duplicate field '{s}'", .{name}); } field_found[field_index] = true; @@ -796,7 +822,7 @@ const Parser = struct { // Parse the union switch (node.get(self.zoir)) { - .enum_literal => |string| { + .enum_literal => |field_name| { // The union must be tagged for an enum literal to coerce to it if (@"union".tag_type == null) { return self.failNode(node, "expected union"); @@ -805,8 +831,9 @@ const Parser = struct { // 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); + const field_name_str = field_name.get(self.zoir); + break :b field_indices.get(field_name_str) orelse + return self.failUnexpected(T, "field", node, null, field_name_str); }; // Initialize the union from the given field. @@ -829,9 +856,10 @@ const Parser = struct { // Fill in the field we found const field_name = struct_fields.names[0]; + const field_name_str = field_name.get(self.zoir); const field_val = struct_fields.vals.at(0); - const field_index = field_indices.get(field_name.get(self.zoir)) orelse - return self.failUnexpectedField(T, node, 0); + const field_index = field_indices.get(field_name_str) orelse + return self.failUnexpected(T, "field", node, 0, field_name_str); switch (field_index) { inline 0...field_infos.len - 1 => |i| { @@ -868,13 +896,29 @@ const Parser = struct { offset: u32, comptime fmt: []const u8, args: anytype, + ) error{ OutOfMemory, ParseZon } { + @branchHint(.cold); + return self.failTokenFmtNote(token, offset, fmt, args, null); + } + + fn failTokenFmtNote( + self: @This(), + token: Ast.TokenIndex, + offset: u32, + comptime fmt: []const u8, + args: anytype, + note: ?Error.TypeCheckFailure.Note, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); if (self.status) |s| s.type_check = .{ .token = token, .offset = offset, - .message = try std.fmt.allocPrint(self.gpa, fmt, args), + .message = std.fmt.allocPrint(self.gpa, fmt, args) catch |err| { + if (note) |n| n.deinit(self.gpa); + return err; + }, .owned = true, + .note = note, }; return error.ParseZon; } @@ -913,6 +957,7 @@ const Parser = struct { .offset = 0, .message = message, .owned = false, + .note = null, }); } @@ -925,11 +970,13 @@ const Parser = struct { return self.failNodeFmt(node, "type '{s}' cannot represent value", .{@typeName(T)}); } - fn failUnexpectedField( + fn failUnexpected( self: @This(), T: type, + item_kind: []const u8, node: Zoir.Node.Index, field: ?usize, + name: []const u8, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); const token = if (field) |f| b: { @@ -943,25 +990,37 @@ const Parser = struct { }; 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); + const note: Error.TypeCheckFailure.Note = if (info.fields.len == 0) b: { + break :b .{ + .token = token, + .offset = 0, + .msg = "none expected", + .owned = false, + }; + } else b: { + const msg = "supported: "; + var buf: std.ArrayListUnmanaged(u8) = try .initCapacity(self.gpa, 64); defer buf.deinit(self.gpa); const writer = buf.writer(self.gpa); try writer.writeAll(msg); inline for (info.fields, 0..) |field_info, i| { if (i != 0) try writer.writeAll(", "); - try writer.print("{}", .{std.zig.fmtId(field_info.name)}); + try writer.print("'{p_}'", .{std.zig.fmtId(field_info.name)}); } - return self.failToken(.{ + break :b .{ .token = token, .offset = 0, - .message = try buf.toOwnedSlice(self.gpa), + .msg = try buf.toOwnedSlice(self.gpa), .owned = true, - }); - } + }; + }; + return self.failTokenFmtNote( + token, + 0, + "unexpected {s} '{s}'", + .{ item_kind, name }, + note, + ); }, else => comptime unreachable, } @@ -1178,7 +1237,10 @@ test "std.zon unions" { fromSlice(Union, gpa, ".{.z=2.5}", &status, .{}), ); try std.testing.expectFmt( - "1:4: error: unexpected field, supported fields: x, y\n", + \\1:4: error: unexpected field 'z' + \\1:4: note: supported: 'x', 'y' + \\ + , "{}", .{status}, ); @@ -1236,7 +1298,10 @@ test "std.zon unions" { defer status.deinit(gpa); try std.testing.expectError(error.ParseZon, fromSlice(Union, gpa, ".y", &status, .{})); try std.testing.expectFmt( - "1:2: error: unexpected field, supported fields: x\n", + \\1:2: error: unexpected field 'y' + \\1:2: note: supported: 'x' + \\ + , "{}", .{status}, ); @@ -1300,7 +1365,10 @@ test "std.zon structs" { fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); try std.testing.expectFmt( - "1:12: error: unexpected field, supported fields: x, y\n", + \\1:12: error: unexpected field 'z' + \\1:12: note: supported: 'x', 'y' + \\ + , "{}", .{status}, ); @@ -1315,7 +1383,7 @@ test "std.zon structs" { error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{}), ); - try std.testing.expectFmt("1:12: error: duplicate struct field name\n", "{}", .{status}); + try std.testing.expectFmt("1:12: error: duplicate field 'x'\n", "{}", .{status}); } // Ignore unknown fields @@ -1336,7 +1404,11 @@ test "std.zon structs" { error.ParseZon, fromSlice(Vec2, gpa, ".{.x=1.5, .z=2.5}", &status, .{}), ); - try std.testing.expectFmt("1:4: error: unexpected field, no fields expected\n", "{}", .{status}); + try std.testing.expectFmt( + \\1:4: error: unexpected field 'x' + \\1:4: note: none expected + \\ + , "{}", .{status}); } // Missing field @@ -2068,7 +2140,10 @@ test "std.zon enum literals" { fromSlice(Enum, gpa, ".qux", &status, .{}), ); try std.testing.expectFmt( - "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", + \\1:2: error: unexpected enum literal 'qux' + \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\ + , "{}", .{status}, ); @@ -2083,7 +2158,10 @@ test "std.zon enum literals" { fromSlice(Enum, gpa, ".@\"foobarbaz\"", &status, .{}), ); try std.testing.expectFmt( - "1:2: error: unexpected field, supported fields: foo, bar, baz, @\"ab\\nc\"\n", + \\1:2: error: unexpected enum literal 'foobarbaz' + \\1:2: note: supported: 'foo', 'bar', 'baz', '@"ab\nc"' + \\ + , "{}", .{status}, ); From 7dba3b1023634331383329137909ec1c6e7191e7 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 28 Jan 2025 18:07:43 -0800 Subject: [PATCH 65/98] Factors some code into failNode --- src/Sema/LowerZon.zig | 186 ++++++++++++++---------------------------- 1 file changed, 60 insertions(+), 126 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index f5028b84c1a9..4acfc42bc8de 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -58,6 +58,16 @@ fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { }; } +fn failNode( + self: LowerZon, + node: Zoir.Node.Index, + comptime format: []const u8, + args: anytype, +) error{ AnalysisFail, OutOfMemory } { + @branchHint(.cold); + return self.fail(.{ .node_abs = node.getAstNode(self.file.zoir.?) }, format, args); +} + fn fail( self: LowerZon, loc: LazySrcLoc.Offset, @@ -96,8 +106,8 @@ fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!I .@"anyframe", .vector, .void, - => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + => return self.failNode( + node, "type '{}' not available in ZON", .{res_ty.fmt(self.sema.pt)}, ), @@ -108,11 +118,7 @@ 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'", - .{}, - ), + else => self.failNode(node, "expected type 'bool'", .{}), }; } @@ -133,8 +139,8 @@ fn lowerInt( 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.?) }, + if (lhs_info.signedness == .unsigned and rhs < 0) return self.failNode( + node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, ); @@ -153,8 +159,8 @@ fn lowerInt( break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; }; if (rhs < min_int or rhs > max_int) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + return self.failNode( + node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, ); @@ -171,8 +177,8 @@ fn lowerInt( if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const int_info = res_ty.intInfo(self.sema.pt.zcu); if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + return self.failNode( + node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), val }, ); @@ -188,8 +194,8 @@ fn lowerInt( .float_literal => |val| { // Check for fractional components if (@rem(val, 1) != 0) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + return self.failNode( + node, "fractional component prevents float value '{}' from coercion to type '{}'", .{ val, res_ty.fmt(self.sema.pt) }, ); @@ -210,8 +216,8 @@ fn lowerInt( // 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.?) }, + return self.failNode( + node, "type '{}' cannot represent integer value '{}'", .{ val, res_ty.fmt(self.sema.pt) }, ); @@ -238,8 +244,8 @@ fn lowerInt( break :b (@as(i64, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; }; if (rhs > max_int) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + return self.failNode( + node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, ); @@ -254,8 +260,8 @@ fn lowerInt( }); }, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + else => return self.failNode( + node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, ), @@ -276,34 +282,30 @@ fn lowerFloat( .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), .pos_inf => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, ); break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128)); }, .neg_inf => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, ); break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128)); }, .nan => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, + if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, ); break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128)); }, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; return value.toIntern(); } @@ -318,7 +320,7 @@ fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPoo 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", .{}), + else => return self.failNode(node, "expected null", .{}), } } @@ -327,19 +329,11 @@ fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I 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)}, - ), + else => return self.failNode(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)}, - ); + return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const elems = try self.sema.arena.alloc( @@ -372,8 +366,8 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In .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.?) }, + return self.failNode( + node, "enum {} has no member named '{}'", .{ res_ty.fmt(self.sema.pt), @@ -386,11 +380,7 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In return value.toIntern(); }, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -406,11 +396,7 @@ fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !Intern ); return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); }, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -431,11 +417,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I 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)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const field_defaults = tuple_info.values.get(ip); @@ -445,9 +427,9 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I 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 }, + const elem_node = elem_nodes.at(@intCast(i)); + return self.failNode( + elem_node, "index {} outside tuple of length {}", .{ elems.len, @@ -458,9 +440,9 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { - const elem_node = elem_nodes.at(@intCast(i)).getAstNode(self.file.zoir.?); - return self.fail( - .{ .node_abs = elem_node }, + const elem_node = elem_nodes.at(@intCast(i)); + return self.failNode( + elem_node, "value stored in comptime field does not match the default value of the field", .{}, ); @@ -475,11 +457,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.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.failNode(node, "missing tuple field with index {}", .{i}); } } @@ -500,11 +478,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const field_defaults = struct_info.field_inits.get(ip); @@ -521,11 +495,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const field_node = fields.vals.at(@intCast(i)); const name_index = struct_info.nameIndex(ip, field_name) orelse { - return self.fail( - .{ .node_abs = field_node.getAstNode(self.file.zoir.?) }, - "unexpected field '{}'", - .{field_name.fmt(ip)}, - ); + return self.failNode(field_node, "unexpected field '{}'", .{field_name.fmt(ip)}); }; const field_type: Type = .fromInterned(struct_info.field_types.get(ip)[name_index]); @@ -566,11 +536,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const field_names = struct_info.field_names.get(ip); for (field_values, field_names) |*value, name| { - if (value.* == .none) return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "missing field '{}'", - .{name.fmt(ip)}, - ); + if (value.* == .none) return self.failNode(node, "missing field '{}'", .{name.fmt(ip)}); } return self.sema.pt.intern(.{ .aggregate = .{ .ty = res_ty.toIntern(), .storage = .{ @@ -585,11 +551,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); if (ptr_info.flags.size != .slice) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "non slice pointers are not available in ZON", - .{}, - ); + return self.failNode(node, "non slice pointers are not available in ZON", .{}); } // String literals @@ -615,11 +577,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool 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)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); @@ -695,18 +653,10 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I .struct_literal => b: { const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, - else => return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return self.failNode(node, "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)}, - ); + return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const field_name = try ip.getOrPutString( self.sema.gpa, @@ -716,19 +666,11 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I ); 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)}, - ), + else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ); + return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); }; const tag_int = if (enum_tag_info.values.len == 0) b: { // Auto numbered fields @@ -747,20 +689,12 @@ 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'", - .{}, - ); + return self.failNode(field_node, "expected type 'void'", .{}); } break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.fail( - .{ .node_abs = node.getAstNode(self.file.zoir.?) }, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ); + return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } break :b .void_value; }; From f62d6221eaed2a80ee9d8c7b3ebc3b64a800a486 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 29 Jan 2025 16:51:33 -0800 Subject: [PATCH 66/98] Checks for duplicate fields in ZonGen instead of LowerZon/parse --- lib/std/zig/ZonGen.zig | 16 +++ lib/std/zon/parse.zig | 19 ++- src/Sema/LowerZon.zig | 113 +++++++----------- .../@import_zon_struct_dup_field.zig | 4 +- ...import_zon_struct_wrong_comptime_field.zig | 2 +- 5 files changed, 71 insertions(+), 83 deletions(-) diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index e0da15865052..0666a889bb1e 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -435,6 +435,22 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator }); try zg.expr(elem_node, @enumFromInt(elem_dest_node)); } + + const names = zg.extra.items[names_start .. names_start + full.ast.fields.len]; + outer: for (0..names.len) |i| { + for (i + 1..names.len) |j| { + if (names[i] == names[j]) { + const first_field_node = full.ast.fields[i]; + const first_token = tree.firstToken(first_field_node) - 2; + const dup_field_node = full.ast.fields[j]; + const dup_token = tree.firstToken(dup_field_node) - 2; + try zg.addErrorTokNotes(first_token, "duplicate struct field name", .{}, &.{ + try zg.errNoteTok(dup_token, "duplicate name here", .{}), + }); + break :outer; + } + } + } }, } } diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 7121469c6482..0c9ef31e7d86 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -696,16 +696,9 @@ const Parser = struct { }; }; - // We now know the array is not zero sized (assert this so the code compiles) + // Mark the field as found. Assert that the found array is not zero length to satisfy + // the type checker (it can't be since we made it into an iteration of this loop.) if (field_found.len == 0) unreachable; - - 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 field '{s}'", .{name}); - } field_found[field_index] = true; switch (field_index) { @@ -1381,9 +1374,13 @@ test "std.zon structs" { defer status.deinit(gpa); try std.testing.expectError( error.ParseZon, - fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5}", &status, .{}), + fromSlice(Vec2, gpa, ".{.x=1.5, .x=2.5, .x=3.5}", &status, .{}), ); - try std.testing.expectFmt("1:12: error: duplicate field 'x'\n", "{}", .{status}); + try std.testing.expectFmt( + \\1:4: error: duplicate struct field name + \\1:12: note: duplicate name here + \\ + , "{}", .{status}); } // Ignore unknown fields diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 4acfc42bc8de..3e3bf01a2914 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -58,24 +58,14 @@ fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { }; } -fn failNode( - self: LowerZon, - node: Zoir.Node.Index, - comptime format: []const u8, - args: anytype, -) error{ AnalysisFail, OutOfMemory } { - @branchHint(.cold); - return self.fail(.{ .node_abs = node.getAstNode(self.file.zoir.?) }, format, args); -} - fn fail( self: LowerZon, - loc: LazySrcLoc.Offset, + node: Zoir.Node.Index, comptime format: []const u8, args: anytype, ) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); - const src_loc = try self.lazySrcLoc(loc); + const src_loc = try self.lazySrcLoc(.{ .node_abs = node.getAstNode(self.file.zoir.?) }); const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); return self.sema.failWithOwnedErrorMsg(self.block, err_msg); @@ -106,7 +96,7 @@ fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!I .@"anyframe", .vector, .void, - => return self.failNode( + => return self.fail( node, "type '{}' not available in ZON", .{res_ty.fmt(self.sema.pt)}, @@ -118,7 +108,7 @@ 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.failNode(node, "expected type 'bool'", .{}), + else => self.fail(node, "expected type 'bool'", .{}), }; } @@ -139,7 +129,7 @@ fn lowerInt( 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.failNode( + if (lhs_info.signedness == .unsigned and rhs < 0) return self.fail( node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, @@ -159,7 +149,7 @@ fn lowerInt( break :b (@as(i32, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; }; if (rhs < min_int or rhs > max_int) { - return self.failNode( + return self.fail( node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, @@ -177,7 +167,7 @@ fn lowerInt( if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const int_info = res_ty.intInfo(self.sema.pt.zcu); if (!val.fitsInTwosComp(int_info.signedness, int_info.bits)) { - return self.failNode( + return self.fail( node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), val }, @@ -194,7 +184,7 @@ fn lowerInt( .float_literal => |val| { // Check for fractional components if (@rem(val, 1) != 0) { - return self.failNode( + return self.fail( node, "fractional component prevents float value '{}' from coercion to type '{}'", .{ val, res_ty.fmt(self.sema.pt) }, @@ -216,7 +206,7 @@ fn lowerInt( // 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.failNode( + return self.fail( node, "type '{}' cannot represent integer value '{}'", .{ val, res_ty.fmt(self.sema.pt) }, @@ -244,7 +234,7 @@ fn lowerInt( break :b (@as(i64, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; }; if (rhs > max_int) { - return self.failNode( + return self.fail( node, "type '{}' cannot represent integer value '{}'", .{ res_ty.fmt(self.sema.pt), rhs }, @@ -260,7 +250,7 @@ fn lowerInt( }); }, - else => return self.failNode( + else => return self.fail( node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, @@ -282,7 +272,7 @@ fn lowerFloat( .float_literal => |val| try self.sema.pt.floatValue(res_ty, val), .char_literal => |val| try self.sema.pt.floatValue(res_ty, @as(f128, @floatFromInt(val))), .pos_inf => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + if (res_ty.toIntern() == .comptime_float_type) return self.fail( node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, @@ -290,7 +280,7 @@ fn lowerFloat( break :b try self.sema.pt.floatValue(res_ty, std.math.inf(f128)); }, .neg_inf => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + if (res_ty.toIntern() == .comptime_float_type) return self.fail( node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, @@ -298,14 +288,14 @@ fn lowerFloat( break :b try self.sema.pt.floatValue(res_ty, -std.math.inf(f128)); }, .nan => b: { - if (res_ty.toIntern() == .comptime_float_type) return self.failNode( + if (res_ty.toIntern() == .comptime_float_type) return self.fail( node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}, ); break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128)); }, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; return value.toIntern(); } @@ -320,7 +310,7 @@ fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPoo fn lowerNull(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { switch (node.get(self.file.zoir.?)) { .null => return .null_value, - else => return self.failNode(node, "expected null", .{}), + else => return self.fail(node, "expected null", .{}), } } @@ -329,11 +319,11 @@ fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I 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.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; if (nodes.len != array_info.len) { - return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const elems = try self.sema.arena.alloc( @@ -366,7 +356,7 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In .no_embedded_nulls, ); const field_index = res_ty.enumFieldIndex(field_name_interned, self.sema.pt.zcu) orelse { - return self.failNode( + return self.fail( node, "enum {} has no member named '{}'", .{ @@ -380,7 +370,7 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In return value.toIntern(); }, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -396,7 +386,7 @@ fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !Intern ); return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); }, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), } } @@ -417,7 +407,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I 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.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const field_defaults = tuple_info.values.get(ip); @@ -428,7 +418,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I for (0..elem_nodes.len) |i| { if (i >= elems.len) { const elem_node = elem_nodes.at(@intCast(i)); - return self.failNode( + return self.fail( elem_node, "index {} outside tuple of length {}", .{ @@ -441,7 +431,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { const elem_node = elem_nodes.at(@intCast(i)); - return self.failNode( + return self.fail( elem_node, "value stored in comptime field does not match the default value of the field", .{}, @@ -457,7 +447,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I for (elems, 0..) |val, i| { if (val == .none) { - return self.failNode(node, "missing tuple field with index {}", .{i}); + return self.fail(node, "missing tuple field with index {}", .{i}); } } @@ -478,12 +468,17 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; - const field_defaults = struct_info.field_inits.get(ip); const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); - @memset(field_values, .none); + + const field_defaults = struct_info.field_inits.get(ip); + if (field_defaults.len > 0) { + @memcpy(field_values, field_defaults); + } else { + @memset(field_values, .none); + } for (0..fields.names.len) |i| { const field_name = try ip.getOrPutString( @@ -495,30 +490,18 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. const field_node = fields.vals.at(@intCast(i)); const name_index = struct_info.nameIndex(ip, field_name) orelse { - return self.failNode(field_node, "unexpected field '{}'", .{field_name.fmt(ip)}); + return self.fail(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) { - const field_node_ast = field_node.getAstNode(self.file.zoir.?); - const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; - return self.fail( - .{ .token_abs = field_name_token }, - "duplicate field '{}'", - .{field_name.fmt(ip)}, - ); - } - field_values[name_index] = try self.lowerExpr(field_node, field_type); if (struct_info.comptime_bits.getBit(ip, name_index)) { const val = ip.indexToKey(field_values[name_index]); const default = ip.indexToKey(field_defaults[name_index]); if (!val.eql(default, ip)) { - const field_node_ast = field_node.getAstNode(self.file.zoir.?); - const field_name_token = self.file.tree.firstToken(field_node_ast) - 2; return self.fail( - .{ .token_abs = field_name_token }, + field_node, "value stored in comptime field does not match the default value of the field", .{}, ); @@ -526,17 +509,9 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. } } - if (field_defaults.len > 0) { - for (field_values, 0..) |*field_value, i| { - if (field_value.* == .none) { - field_value.* = field_defaults[i]; - } - } - } - const field_names = struct_info.field_names.get(ip); for (field_values, field_names) |*value, name| { - if (value.* == .none) return self.failNode(node, "missing field '{}'", .{name.fmt(ip)}); + if (value.* == .none) return self.fail(node, "missing field '{}'", .{name.fmt(ip)}); } return self.sema.pt.intern(.{ .aggregate = .{ .ty = res_ty.toIntern(), .storage = .{ @@ -551,7 +526,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); if (ptr_info.flags.size != .slice) { - return self.failNode(node, "non slice pointers are not available in ZON", .{}); + return self.fail(node, "non slice pointers are not available in ZON", .{}); } // String literals @@ -577,7 +552,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool 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.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); @@ -653,10 +628,10 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I .struct_literal => b: { const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; if (fields.names.len != 1) { - return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } const field_name = try ip.getOrPutString( self.sema.gpa, @@ -666,11 +641,11 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I ); break :b .{ field_name, fields.vals.at(0) }; }, - else => return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { - return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); }; const tag_int = if (enum_tag_info.values.len == 0) b: { // Auto numbered fields @@ -689,12 +664,12 @@ 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.failNode(field_node, "expected type 'void'", .{}); + return self.fail(field_node, "expected type 'void'", .{}); } break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.failNode(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); } break :b .void_value; }; 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 ff9cb12f82ff..f8d2771eb5e1 100644 --- a/test/cases/compile_errors/@import_zon_struct_dup_field.zig +++ b/test/cases/compile_errors/@import_zon_struct_dup_field.zig @@ -7,5 +7,5 @@ export fn entry() void { // error // imports=zon/struct_dup_field.zon // -// struct_dup_field.zon:3:6: error: duplicate field 'name' -// tmp.zig:3:44: note: imported here +// struct_dup_field.zon:2:6: error: duplicate struct field name +// struct_dup_field.zon:3:6: note: duplicate name here diff --git a/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig b/test/cases/compile_errors/@import_zon_struct_wrong_comptime_field.zig index ef818499d6e4..503c9db33f46 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 // -// vec2.zon:1:15: error: value stored in comptime field does not match the default value of the field +// vec2.zon:1:19: error: value stored in comptime field does not match the default value of the field // tmp.zig:6:29: note: imported here From b0229a96740ca3bd6ccd34aea12a618dc60c4e85 Mon Sep 17 00:00:00 2001 From: mlugg Date: Thu, 30 Jan 2025 01:36:21 +0000 Subject: [PATCH 67/98] ZonGen: detect duplicate field names with a hashmap --- lib/std/zig/ZonGen.zig | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/std/zig/ZonGen.zig b/lib/std/zig/ZonGen.zig index 0666a889bb1e..c50cf83538c1 100644 --- a/lib/std/zig/ZonGen.zig +++ b/lib/std/zig/ZonGen.zig @@ -427,29 +427,32 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator .ast_node = node, }); + // For short initializers, track the names on the stack rather than going through gpa. + var sfba_state = std.heap.stackFallback(256, gpa); + const sfba = sfba_state.get(); + var field_names: std.AutoHashMapUnmanaged(Zoir.NullTerminatedString, Ast.TokenIndex) = .empty; + defer field_names.deinit(sfba); + + var reported_any_duplicate = false; + for (full.ast.fields, names_start.., first_elem..) |elem_node, extra_name_idx, elem_dest_node| { const name_token = tree.firstToken(elem_node) - 2; - zg.extra.items[extra_name_idx] = @intFromEnum(zg.identAsString(name_token) catch |err| switch (err) { - error.BadString => undefined, // doesn't matter, there's an error - error.OutOfMemory => |e| return e, - }); - try zg.expr(elem_node, @enumFromInt(elem_dest_node)); - } - - const names = zg.extra.items[names_start .. names_start + full.ast.fields.len]; - outer: for (0..names.len) |i| { - for (i + 1..names.len) |j| { - if (names[i] == names[j]) { - const first_field_node = full.ast.fields[i]; - const first_token = tree.firstToken(first_field_node) - 2; - const dup_field_node = full.ast.fields[j]; - const dup_token = tree.firstToken(dup_field_node) - 2; - try zg.addErrorTokNotes(first_token, "duplicate struct field name", .{}, &.{ - try zg.errNoteTok(dup_token, "duplicate name here", .{}), + if (zg.identAsString(name_token)) |name_str| { + zg.extra.items[extra_name_idx] = @intFromEnum(name_str); + const gop = try field_names.getOrPut(sfba, name_str); + if (gop.found_existing and !reported_any_duplicate) { + reported_any_duplicate = true; + const earlier_token = gop.value_ptr.*; + try zg.addErrorTokNotes(earlier_token, "duplicate struct field name", .{}, &.{ + try zg.errNoteTok(name_token, "duplicate name here", .{}), }); - break :outer; } + gop.value_ptr.* = name_token; + } else |err| switch (err) { + error.BadString => {}, // there's an error, so it's fine to not populate `zg.extra` + error.OutOfMemory => |e| return e, } + try zg.expr(elem_node, @enumFromInt(elem_dest_node)); } }, } From 9c8d93d6923eb1bc7a805d290c4426e1f7c76210 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 29 Jan 2025 17:46:00 -0800 Subject: [PATCH 68/98] Fixes lower pointer --- src/Sema/LowerZon.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 3e3bf01a2914..1992fb89cbba 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -592,7 +592,7 @@ fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool .ty = many_item_ptr_type, .base_addr = .{ .uav = .{ - .orig_ty = res_ty.toIntern(), + .orig_ty = (try self.sema.pt.singleConstPtrType(.fromInterned(array_ty))).toIntern(), .val = array, }, }, From cfd987608f72d230f8219510bbb5a0d9df3b8169 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 29 Jan 2025 18:05:10 -0800 Subject: [PATCH 69/98] Zoir char literals are now u21s instead of u32s --- lib/std/zig/Zoir.zig | 4 ++-- src/Sema/LowerZon.zig | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/std/zig/Zoir.zig b/lib/std/zig/Zoir.zig index 9a9bc419763f..af93d03261ff 100644 --- a/lib/std/zig/Zoir.zig +++ b/lib/std/zig/Zoir.zig @@ -54,7 +54,7 @@ pub const Node = union(enum) { /// A floating-point literal. float_literal: f128, /// A Unicode codepoint literal. - char_literal: u32, + char_literal: u21, /// An enum literal. The string is the literal, i.e. `foo` for `.foo`. enum_literal: NullTerminatedString, /// A string literal. @@ -96,7 +96,7 @@ pub const Node = union(enum) { } } }, .float_literal_small => .{ .float_literal = @as(f32, @bitCast(repr.data)) }, .float_literal => .{ .float_literal = @bitCast(zoir.extra[repr.data..][0..4].*) }, - .char_literal => .{ .char_literal = repr.data }, + .char_literal => .{ .char_literal = @intCast(repr.data) }, .enum_literal => .{ .enum_literal = @enumFromInt(repr.data) }, .string_literal => .{ .string_literal = s: { const start, const len = zoir.extra[repr.data..][0..2].*; diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 1992fb89cbba..42f2322dd42a 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -221,17 +221,18 @@ fn lowerInt( }); }, .char_literal => |val| { - const rhs: u32 = val; + const rhs: u21 = val; // If our result is a fixed size integer, check that our value is not out of bounds if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { const lhs_info = res_ty.intInfo(self.sema.pt.zcu); - // If lhs has less than 64 bits, we bounds check. We check at 64 instead of 32 in + // If lhs has less than 42 bits, we bounds check. We check at 42 instead of 21 in // case LHS is signed. - if (std.math.cast(u6, lhs_info.bits)) |bits| { - const max_int: i64 = if (bits == 0) b: { + if (lhs_info.bits < 42) { + const bits: u6 = @intCast(lhs_info.bits); + const max_int: i42 = if (bits == 0) b: { break :b 0; } else b: { - break :b (@as(i64, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; + break :b (@as(i42, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; }; if (rhs > max_int) { return self.fail( From 0e2634a84ed664b20673c7d6734f1dd41d61bfb2 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 29 Jan 2025 18:15:18 -0800 Subject: [PATCH 70/98] Stops relying on default field slice staying valid --- src/Sema/LowerZon.zig | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 42f2322dd42a..4a5d4bc87b97 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -411,10 +411,15 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; - const field_defaults = tuple_info.values.get(ip); const field_types = tuple_info.types.get(ip); const elems = try self.sema.arena.alloc(InternPool.Index, field_types.len); - @memset(elems, .none); + + const field_comptime_vals = tuple_info.values.get(ip); + if (field_comptime_vals.len > 0) { + @memcpy(elems, field_comptime_vals); + } else { + @memset(elems, .none); + } for (0..elem_nodes.len) |i| { if (i >= elems.len) { @@ -428,9 +433,10 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I }, ); } - elems[i] = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); - if (field_defaults[i] != .none and elems[i] != field_defaults[i]) { + const val = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(field_types[i])); + + if (elems[i] != .none and val != elems[i]) { const elem_node = elem_nodes.at(@intCast(i)); return self.fail( elem_node, @@ -438,12 +444,8 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I .{}, ); } - } - for (elems, 0..) |*elem, i| { - if (elem.* == .none and i < field_defaults.len) { - elem.* = field_defaults[i]; - } + elems[i] = val; } for (elems, 0..) |val, i| { From 745774163e471a13656708567ff6609e8e87701a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 29 Jan 2025 18:25:12 -0800 Subject: [PATCH 71/98] Resolves more feedback --- lib/std/zon/parse.zig | 18 ++++++------------ src/Sema/LowerZon.zig | 26 ++++++++------------------ 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 0c9ef31e7d86..8c02abe1f5bf 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -356,9 +356,7 @@ pub fn free(gpa: Allocator, value: anytype) void { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { switch (pointer.size) { - .one, .many, .c => if (comptime requiresAllocator(Value)) { - @compileError(@typeName(Value) ++ ": free cannot free non slice pointers"); - }, + .one, .many, .c => if (comptime requiresAllocator(Value)) unreachable, .slice => for (value) |item| { free(gpa, item); }, @@ -372,9 +370,7 @@ pub fn free(gpa: Allocator, value: anytype) void { free(gpa, @field(value, field.name)); }, .@"union" => |@"union"| if (@"union".tag_type == null) { - if (comptime requiresAllocator(Value)) { - @compileError(@typeName(Value) ++ ": free cannot free untagged unions"); - } + if (comptime requiresAllocator(Value)) unreachable; } else switch (value) { inline else => |_, tag| { free(gpa, @field(value, @tagName(tag))); @@ -385,7 +381,7 @@ pub fn free(gpa: Allocator, value: anytype) void { }, .void => {}, .null => {}, - else => @compileError(@typeName(Value) ++ ": free cannot free this type"), + else => comptime unreachable, } } @@ -436,7 +432,7 @@ const Parser = struct { .@"union" => return self.parseUnion(T, options, node), .optional => return self.parseOptional(T, options, node), - else => @compileError("type '" ++ @typeName(T) ++ "' is not available in ZON"), + else => comptime unreachable, } } @@ -574,7 +570,7 @@ const Parser = struct { // Make sure we're working with a slice switch (pointer.size) { .slice => {}, - .one, .many, .c => @compileError(@typeName(T) ++ ": non slice pointers not supported"), + .one, .many, .c => comptime unreachable, } // Allocate the slice @@ -800,9 +796,7 @@ const Parser = struct { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; - if (field_infos.len == 0) { - @compileError(@typeName(T) ++ ": cannot parse unions with no fields"); - } + if (field_infos.len == 0) comptime unreachable; // Gather info on the fields const field_indices = b: { diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 4a5d4bc87b97..d16402266130 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -517,9 +517,12 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. if (value.* == .none) return self.fail(node, "missing field '{}'", .{name.fmt(ip)}); } - return self.sema.pt.intern(.{ .aggregate = .{ .ty = res_ty.toIntern(), .storage = .{ - .elems = field_values, - } } }); + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ + .elems = field_values, + }, + } }); } fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { @@ -650,20 +653,7 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); }; - const tag_int = if (enum_tag_info.values.len == 0) b: { - // Auto numbered fields - break :b try self.sema.pt.intern(.{ .int = .{ - .ty = enum_tag_info.tag_ty, - .storage = .{ .u64 = name_index }, - } }); - } else b: { - // Explicitly numbered fields - break :b enum_tag_info.values.get(ip)[name_index]; - }; - const tag = try self.sema.pt.intern(.{ .enum_tag = .{ - .ty = union_info.enum_tag_ty, - .int = tag_int, - } }); + const tag = try self.sema.pt.enumValueFieldIndex(.fromInterned(union_info.enum_tag_ty), name_index); const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]); const val = if (maybe_field_node) |field_node| b: { if (field_type.toIntern() == .void_type) { @@ -678,7 +668,7 @@ fn lowerUnion(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I }; return ip.getUnion(self.sema.pt.zcu.gpa, self.sema.pt.tid, .{ .ty = res_ty.toIntern(), - .tag = tag, + .tag = tag.toIntern(), .val = val, }); } From 8788aaf9e43b3522ef28a5a06edb722ce6d9d806 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 30 Jan 2025 19:52:36 -0800 Subject: [PATCH 72/98] Simplifies bounds check --- src/Sema/LowerZon.zig | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index d16402266130..5f638af821bf 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -221,24 +221,17 @@ fn lowerInt( }); }, .char_literal => |val| { - const rhs: u21 = val; // If our result is a fixed size integer, check that our value is not out of bounds if (res_ty.zigTypeTag(self.sema.pt.zcu) == .int) { - const lhs_info = res_ty.intInfo(self.sema.pt.zcu); - // If lhs has less than 42 bits, we bounds check. We check at 42 instead of 21 in - // case LHS is signed. - if (lhs_info.bits < 42) { - const bits: u6 = @intCast(lhs_info.bits); - const max_int: i42 = if (bits == 0) b: { - break :b 0; - } else b: { - break :b (@as(i42, 1) << (bits - @intFromBool(lhs_info.signedness == .signed))) - 1; - }; - if (rhs > max_int) { + const dest_info = res_ty.intInfo(self.sema.pt.zcu); + const unsigned_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed); + if (unsigned_bits < 21) { + const out_of_range: u21 = @as(u21, 1) << @intCast(unsigned_bits); + if (val >= out_of_range) { return self.fail( node, "type '{}' cannot represent integer value '{}'", - .{ res_ty.fmt(self.sema.pt), rhs }, + .{ res_ty.fmt(self.sema.pt), val }, ); } } @@ -246,7 +239,7 @@ fn lowerInt( return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ .ty = res_ty.toIntern(), - .storage = .{ .i64 = rhs }, + .storage = .{ .i64 = val }, }, }); }, From c1cca4bf308a106557f700c16b63d56fedddf7a0 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 30 Jan 2025 20:03:03 -0800 Subject: [PATCH 73/98] Caches result of track zir --- src/Sema/LowerZon.zig | 44 +++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 5f638af821bf..44761d053b48 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -25,6 +25,7 @@ file: *File, file_index: Zcu.File.Index, import_loc: LazySrcLoc, block: *Sema.Block, +base_node_inst: InternPool.TrackedInst.Index.Optional = .none, /// Lowers the given file as ZON. pub fn lower( @@ -36,7 +37,7 @@ pub fn lower( block: *Sema.Block, ) CompileError!InternPool.Index { _ = try file.getZoir(sema.pt.zcu); - const lower_zon: LowerZon = .{ + var lower_zon: LowerZon = .{ .sema = sema, .file = file, .file_index = file_index, @@ -47,19 +48,22 @@ pub fn lower( return lower_zon.lowerExpr(.root, res_ty); } -fn lazySrcLoc(self: LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { - return .{ - .base_node_inst = try self.sema.pt.zcu.intern_pool.trackZir( +fn lazySrcLoc(self: *LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { + if (self.base_node_inst == .none) { + self.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 }, - ), + )).toOptional(); + } + return .{ + .base_node_inst = self.base_node_inst.unwrap().?, .offset = loc, }; } fn fail( - self: LowerZon, + self: *LowerZon, node: Zoir.Node.Index, comptime format: []const u8, args: anytype, @@ -71,7 +75,7 @@ fn fail( return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } -fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { +fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { switch (res_ty.zigTypeTag(self.sema.pt.zcu)) { .bool => return self.lowerBool(node), .int, .comptime_int => return self.lowerInt(node, res_ty), @@ -104,7 +108,7 @@ fn lowerExpr(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!I } } -fn lowerBool(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { +fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { return switch (node.get(self.file.zoir.?)) { .true => .bool_true, .false => .bool_false, @@ -113,7 +117,7 @@ fn lowerBool(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { } fn lowerInt( - self: LowerZon, + self: *LowerZon, node: Zoir.Node.Index, res_ty: Type, ) !InternPool.Index { @@ -253,7 +257,7 @@ fn lowerInt( } fn lowerFloat( - self: LowerZon, + self: *LowerZon, node: Zoir.Node.Index, res_ty: Type, ) !InternPool.Index { @@ -294,21 +298,21 @@ fn lowerFloat( return value.toIntern(); } -fn lowerOptional(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerOptional(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { return switch (node.get(self.file.zoir.?)) { .null => .null_value, else => try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), }; } -fn lowerNull(self: LowerZon, node: Zoir.Node.Index) !InternPool.Index { +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, "expected null", .{}), } } -fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const array_info = res_ty.arrayInfo(self.sema.pt.zcu); const nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { .array_literal => |nodes| nodes, @@ -339,7 +343,7 @@ fn lowerArray(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I } }); } -fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +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| { @@ -368,7 +372,7 @@ fn lowerEnum(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.In } } -fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerEnumLiteral(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| { @@ -384,7 +388,7 @@ fn lowerEnumLiteral(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !Intern } } -fn lowerStructOrTuple(self: LowerZon, node: Zoir.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), @@ -393,7 +397,7 @@ 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 { +fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const tuple_info = ip.indexToKey(res_ty.toIntern()).tuple_type; @@ -453,7 +457,7 @@ fn lowerTuple(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I } }); } -fn lowerStruct(self: LowerZon, node: Zoir.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; @@ -518,7 +522,7 @@ fn lowerStruct(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. } }); } -fn lowerPointer(self: LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +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; @@ -608,7 +612,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 { +fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; try res_ty.resolveFields(self.sema.pt); const union_info = self.sema.pt.zcu.typeToUnion(res_ty).?; From 28044a7b466afb1eaed5dc8932901221213e6c76 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Thu, 30 Jan 2025 20:57:19 -0800 Subject: [PATCH 74/98] Supports parsing vectors at runtime, also makes parser options a runtime parameter --- lib/std/zon/parse.zig | 238 ++++++++++++++++++++++++++---------------- 1 file changed, 148 insertions(+), 90 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 8c02abe1f5bf..c180234ccbe3 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -282,7 +282,7 @@ pub fn fromSlice( gpa: Allocator, source: [:0]const u8, status: ?*Status, - comptime options: Options, + options: Options, ) error{ OutOfMemory, ParseZon }!T { if (status) |s| s.assertEmpty(); @@ -306,7 +306,7 @@ pub fn fromZoir( ast: Ast, zoir: Zoir, status: ?*Status, - comptime options: Options, + options: Options, ) error{ OutOfMemory, ParseZon }!T { return fromZoirNode(T, gpa, ast, zoir, .root, status, options); } @@ -319,7 +319,7 @@ pub fn fromZoirNode( zoir: Zoir, node: Zoir.Node.Index, status: ?*Status, - comptime options: Options, + options: Options, ) error{ OutOfMemory, ParseZon }!T { if (status) |s| { s.assertEmpty(); @@ -335,10 +335,11 @@ pub fn fromZoirNode( .gpa = gpa, .ast = ast, .zoir = zoir, + .options = options, .status = status, }; - return parser.parseExpr(T, options, node); + return parser.parseExpr(T, node); } /// Frees ZON values. @@ -410,27 +411,24 @@ const Parser = struct { ast: Ast, zoir: Zoir, status: ?*Status, + options: Options, - fn parseExpr( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parseExpr(self: *@This(), comptime T: type, 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), + .pointer => return self.parsePointer(T, node), + .array => return self.parseArray(T, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) - return self.parseTuple(T, options, node) + return self.parseTuple(T, node) else - return self.parseStruct(T, options, node), - .@"union" => return self.parseUnion(T, options, node), - .optional => return self.parseOptional(T, options, node), + return self.parseStruct(T, node), + .@"union" => return self.parseUnion(T, node), + .optional => return self.parseOptional(T, node), + .vector => return self.parseVector(T, node), else => comptime unreachable, } @@ -444,11 +442,7 @@ const Parser = struct { } } - fn parseInt( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, - ) !T { + 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 @@ -465,11 +459,7 @@ const Parser = struct { } } - fn parseFloat( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, - ) !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), @@ -484,11 +474,7 @@ const Parser = struct { } } - fn parseEnumLiteral( - self: @This(), - comptime T: type, - node: Zoir.Node.Index, - ) !T { + fn parseEnumLiteral(self: @This(), comptime T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .enum_literal => |field_name| { // Create a comptime string map for the enum fields @@ -508,25 +494,16 @@ const Parser = struct { } } - fn parsePointer( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parsePointer(self: *@This(), comptime T: type, 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 }), + .array_literal => |nodes| return try self.parseSlice(T, nodes), + .empty_literal => return try self.parseSlice(T, .{ .start = node, .len = 0 }), else => return self.failExpectedContainer(T, node), } } - fn parseString( - self: *@This(), - comptime T: type, - node: Zoir.Node.Index, - ) !T { + 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); @@ -559,12 +536,7 @@ const Parser = struct { } } - fn parseSlice( - self: *@This(), - comptime T: type, - comptime options: Options, - nodes: Zoir.Node.Index.Range, - ) !T { + fn parseSlice(self: *@This(), comptime T: type, nodes: Zoir.Node.Index.Range) !T { const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice @@ -584,23 +556,18 @@ const Parser = struct { // Parse the elements and return the slice for (slice, 0..) |*elem, i| { - errdefer if (options.free_on_error) { + errdefer if (self.options.free_on_error) { for (slice[0..i]) |item| { free(self.gpa, item); } }; - elem.* = try self.parseExpr(pointer.child, options, nodes.at(@intCast(i))); + elem.* = try self.parseExpr(pointer.child, nodes.at(@intCast(i))); } return slice; } - fn parseArray( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parseArray(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -628,23 +595,18 @@ const Parser = struct { var result: T = undefined; for (&result, 0..) |*elem, i| { // If we fail to parse this field, free all fields before it - errdefer if (options.free_on_error) { + errdefer if (self.options.free_on_error) { for (result[0..i]) |item| { free(self.gpa, item); } }; - elem.* = try self.parseExpr(array_info.child, options, nodes.at(@intCast(i))); + elem.* = try self.parseExpr(array_info.child, nodes.at(@intCast(i))); } return result; } - fn parseStruct( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parseStruct(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { const repr = node.get(self.zoir); const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, @@ -669,7 +631,7 @@ const Parser = struct { // 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) { + errdefer if (self.options.free_on_error and field_infos.len > 0) { for (fields.names[0..initialized]) |name_runtime| { switch (field_indices.get(name_runtime.get(self.zoir)) orelse continue) { inline 0...(field_infos.len - 1) => |name_index| { @@ -685,7 +647,7 @@ const Parser = struct { for (0..fields.names.len) |i| { const name = fields.names[i].get(self.zoir); const field_index = b: { - break :b field_indices.get(name) orelse if (options.ignore_unknown_fields) { + break :b field_indices.get(name) orelse if (self.options.ignore_unknown_fields) { continue; } else { return self.failUnexpected(T, "field", node, i, name); @@ -704,7 +666,6 @@ const Parser = struct { } else { @field(result, field_infos[j].name) = try self.parseExpr( field_infos[j].type, - options, fields.vals.at(@intCast(i)), ); } @@ -735,12 +696,7 @@ const Parser = struct { return result; } - fn parseTuple( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parseTuple(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -769,7 +725,7 @@ const Parser = struct { } } else { // If we fail to parse this field, free all fields before it - errdefer if (options.free_on_error) { + errdefer if (self.options.free_on_error) { inline for (0..i) |j| { if (j >= i) break; free(self.gpa, result[j]); @@ -779,7 +735,7 @@ const Parser = struct { 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)); + result[i] = try self.parseExpr(field_infos[i].type, nodes.at(i)); } } } @@ -787,12 +743,7 @@ const Parser = struct { return result; } - fn parseUnion( - self: *@This(), - comptime T: type, - comptime options: Options, - node: Zoir.Node.Index, - ) !T { + fn parseUnion(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; @@ -853,7 +804,7 @@ const Parser = struct { 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); + const value = try self.parseExpr(field_infos[i].type, field_val); return @unionInit(T, field_infos[i].name, value); } }, @@ -864,17 +815,42 @@ const Parser = struct { } } - fn parseOptional( + fn parseOptional(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + if (node.get(self.zoir) == .null) { + return null; + } + + return try self.parseExpr(@typeInfo(T).optional.child, node); + } + + fn parseVector( self: *@This(), comptime T: type, - comptime options: Options, node: Zoir.Node.Index, ) !T { - if (node.get(self.zoir) == .null) { - return null; + const vector_info = @typeInfo(T).vector; + + const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.failExpectedContainer(T, node), + }; + + var result: T = undefined; + + if (nodes.len != vector_info.len) { + return self.failNodeFmt( + node, + "expected {} vector elements; found {}", + .{ vector_info.len, nodes.len }, + ); + } + + for (0..vector_info.len) |i| { + result[i] = try self.parseExpr(vector_info.child, nodes.at(@intCast(i))); } - return try self.parseExpr(@typeInfo(T).optional.child, options, node); + return result; } fn failTokenFmt( @@ -1013,7 +989,11 @@ const Parser = struct { } } - fn failExpectedContainer(self: @This(), T: type, node: Zoir.Node.Index) error{ OutOfMemory, 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) { @@ -1035,6 +1015,7 @@ const Parser = struct { return self.failNode(node, "expected tuple"); } }, + .vector => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), else => {}, } comptime unreachable; @@ -2896,3 +2877,80 @@ test "std.zon free on error" { try std.testing.expectEqualStrings("bar", result[1].x); } } + +test "vector" { + const gpa = std.testing.allocator; + + // Passing cases + try std.testing.expectEqual( + @Vector(0, bool){}, + try fromSlice(@Vector(0, bool), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, bool){ true, false, true }, + try fromSlice(@Vector(3, bool), gpa, ".{true, false, true}", null, .{}), + ); + + try std.testing.expectEqual( + @Vector(0, f32){}, + try fromSlice(@Vector(0, f32), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, f32){ 1.5, 2.5, 3.5 }, + try fromSlice(@Vector(3, f32), gpa, ".{1.5, 2.5, 3.5}", null, .{}), + ); + + try std.testing.expectEqual( + @Vector(0, u8){}, + try fromSlice(@Vector(0, u8), gpa, ".{}", null, .{}), + ); + try std.testing.expectEqual( + @Vector(3, u8){ 2, 4, 6 }, + try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}), + ); + + // Too few fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(2, f32), gpa, ".{0.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 2 vector elements; found 1\n", + "{}", + .{status}, + ); + } + + // Too many fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(2, f32), gpa, ".{0.5, 1.5, 2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:2: error: expected 2 vector elements; found 3\n", + "{}", + .{status}, + ); + } + + // Wrong type fields + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, f32), gpa, ".{0.5, true, 2.5}", &status, .{}), + ); + try std.testing.expectFmt( + "1:8: error: expected type 'f32'\n", + "{}", + .{status}, + ); + } +} From 093a02a4297e18df26b112f0e34cc28f78d82784 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 31 Jan 2025 16:50:56 -0800 Subject: [PATCH 75/98] Adds support for parsing vectors at comptime --- src/Sema/LowerZon.zig | 33 ++++++++++++++++- test/behavior/zon.zig | 35 +++++++++++++++++++ test/behavior/zon/vec3_bool.zon | 1 + test/behavior/zon/vec3_float.zon | 1 + test/behavior/zon/vec3_int.zon | 1 + .../@import_zon_vec_too_few.zig | 10 ++++++ .../@import_zon_vec_too_many.zig | 10 ++++++ .../@import_zon_vec_wrong_type.zig | 10 ++++++ 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test/behavior/zon/vec3_bool.zon create mode 100644 test/behavior/zon/vec3_float.zon create mode 100644 test/behavior/zon/vec3_int.zon create mode 100644 test/cases/compile_errors/@import_zon_vec_too_few.zig create mode 100644 test/cases/compile_errors/@import_zon_vec_too_many.zig create mode 100644 test/cases/compile_errors/@import_zon_vec_wrong_type.zig diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 44761d053b48..d16da755e11b 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -88,6 +88,7 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! .@"struct" => return self.lowerStructOrTuple(node, res_ty), .@"union" => return self.lowerUnion(node, res_ty), .pointer => return self.lowerPointer(node, res_ty), + .vector => return self.lowerVector(node, res_ty), .type, .noreturn, @@ -98,7 +99,6 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! .@"opaque", .frame, .@"anyframe", - .vector, .void, => return self.fail( node, @@ -669,3 +669,34 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. .val = val, }); } + +fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { + const ip = &self.sema.pt.zcu.intern_pool; + + const vector_info = ip.indexToKey(res_ty.toIntern()).vector_type; + + const elem_nodes: Zoir.Node.Index.Range = switch (node.get(self.file.zoir.?)) { + .array_literal => |nodes| nodes, + .empty_literal => .{ .start = node, .len = 0 }, + else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + }; + + const elems = try self.sema.arena.alloc(InternPool.Index, vector_info.len); + + if (elem_nodes.len != vector_info.len) { + return self.fail( + node, + "expected {} vector elements; found {}", + .{ vector_info.len, elem_nodes.len }, + ); + } + + for (elems, 0..) |*elem, i| { + elem.* = try self.lowerExpr(elem_nodes.at(@intCast(i)), .fromInterned(vector_info.child)); + } + + return self.sema.pt.intern(.{ .aggregate = .{ + .ty = res_ty.toIntern(), + .storage = .{ .elems = elems }, + } }); +} diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 1d14e4608845..8f3a5d87213a 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -407,3 +407,38 @@ test "inf and nan" { try expect(std.math.isNegativeInf(actual[2])); } } + +test "vector" { + { + const actual: @Vector(0, bool) = @import("zon/vec0.zon"); + const expected: @Vector(0, bool) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, bool) = @import("zon/vec3_bool.zon"); + const expected: @Vector(3, bool) = .{ false, false, true }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, f32) = @import("zon/vec0.zon"); + const expected: @Vector(0, f32) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, f32) = @import("zon/vec3_float.zon"); + const expected: @Vector(3, f32) = .{ 1.5, 2.5, 3.5 }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, u8) = @import("zon/vec3_int.zon"); + const expected: @Vector(3, u8) = .{ 2, 4, 6 }; + try expectEqual(expected, actual); + } +} diff --git a/test/behavior/zon/vec3_bool.zon b/test/behavior/zon/vec3_bool.zon new file mode 100644 index 000000000000..53aff974193b --- /dev/null +++ b/test/behavior/zon/vec3_bool.zon @@ -0,0 +1 @@ +.{ false, false, true } diff --git a/test/behavior/zon/vec3_float.zon b/test/behavior/zon/vec3_float.zon new file mode 100644 index 000000000000..a0e225db099e --- /dev/null +++ b/test/behavior/zon/vec3_float.zon @@ -0,0 +1 @@ +.{ 1.5, 2.5, 3.5 } diff --git a/test/behavior/zon/vec3_int.zon b/test/behavior/zon/vec3_int.zon new file mode 100644 index 000000000000..210cd1a2d6c8 --- /dev/null +++ b/test/behavior/zon/vec3_int.zon @@ -0,0 +1 @@ +.{ 2, 4, 6 } diff --git a/test/cases/compile_errors/@import_zon_vec_too_few.zig b/test/cases/compile_errors/@import_zon_vec_too_few.zig new file mode 100644 index 000000000000..a44e3886c5e5 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_too_few.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(3, f32) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// zon/tuple.zon:1:2: error: expected 3 vector elements; found 2 +// tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_too_many.zig b/test/cases/compile_errors/@import_zon_vec_too_many.zig new file mode 100644 index 000000000000..8e55c8c4051c --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_too_many.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(1, f32) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// zon/tuple.zon:1:2: error: expected 1 vector elements; found 2 +// tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_wrong_type.zig b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig new file mode 100644 index 000000000000..24b4801f2803 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: @Vector(2, bool) = @import("zon/tuple.zon"); + _ = f; +} + +// error +// imports=zon/tuple.zon +// +// zon/tuple.zon:1:4: error: expected type 'bool' +// tmp.zig:2:41: note: imported here From 2af30a4a19db607e917f4570eab060c70b1788a1 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 31 Jan 2025 17:01:42 -0800 Subject: [PATCH 76/98] Adds support for serializing vectors, cleans up asserts --- lib/std/zon/parse.zig | 2 +- lib/std/zon/stringify.zig | 70 +++++++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index c180234ccbe3..0d5a57503633 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -2878,7 +2878,7 @@ test "std.zon free on error" { } } -test "vector" { +test "std.zon vector" { const gpa = std.testing.allocator; // Passing cases diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 6465369ec29d..522deb6501e7 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -21,6 +21,7 @@ //! Unsupported types will fail to serialize at compile time. const std = @import("std"); +const assert = std.debug.assert; /// Options for `serialize`. pub const SerializeOptions = struct { @@ -374,20 +375,18 @@ pub fn Serializer(Writer: type) type { .float, .comptime_float => try self.float(val), .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), .enum_literal => try self.ident(@tagName(val)), - .@"enum" => |@"enum"| if (@"enum".is_exhaustive) { - try self.ident(@tagName(val)); - } else { - @compileError( - @typeName(@TypeOf(val)) ++ ": cannot stringify non-exhaustive enums", - ); + .@"enum" => |@"enum"| b: { + comptime assert(@"enum".is_exhaustive); + break :b try self.ident(@tagName(val)); }, .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 => b: { + comptime assert(pointer.size == .slice); + break :b pointer.child; + }, }; const sentinel = pointer.sentinel(); if (child_type == u8 and @@ -451,9 +450,8 @@ pub fn Serializer(Writer: type) type { } try container.finish(); }, - .@"union" => |@"union"| if (@"union".tag_type == null) { - @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify untagged unions"); - } else { + .@"union" => |@"union"| { + comptime assert(@"union".tag_type != null); var container = try self.startStruct(.{ .whitespace_style = .{ .fields = 1 } }); switch (val) { inline else => |pl, tag| try container.fieldArbitraryDepth( @@ -469,8 +467,17 @@ pub fn Serializer(Writer: type) type { } else { try self.writer.writeAll("null"); }, + .vector => |vector| { + var container = try self.startTuple( + .{ .whitespace_style = .{ .fields = vector.len } }, + ); + for (0..vector.len) |i| { + try container.fieldArbitraryDepth(val[i], options); + } + try container.finish(); + }, - else => @compileError(@typeName(@TypeOf(val)) ++ ": cannot stringify this type"), + else => comptime unreachable, } } @@ -492,7 +499,7 @@ pub fn Serializer(Writer: type) type { try std.fmt.format(self.writer, "{d}", .{val}); }, .comptime_float => try std.fmt.format(self.writer, "{d}", .{val}), - else => @compileError(@typeName(@TypeOf(val)) ++ ": expected float"), + else => comptime unreachable, } } @@ -2089,3 +2096,38 @@ test "std.zon stringify as float" { try std.testing.expectEqualStrings("2.5", buf.items); buf.clearRetainingCapacity(); } + +test "std.zon stringify vector" { + try expectSerializeEqual( + \\.{ + \\ .{}, + \\ .{ + \\ true, + \\ false, + \\ true, + \\ }, + \\ .{}, + \\ .{ + \\ 1.5, + \\ 2.5, + \\ 3.5, + \\ }, + \\ .{}, + \\ .{ + \\ 2, + \\ 4, + \\ 6, + \\ }, + \\} + , + .{ + @Vector(0, bool){}, + @Vector(3, bool){ true, false, true }, + @Vector(0, f32){}, + @Vector(3, f32){ 1.5, 2.5, 3.5 }, + @Vector(0, u8){}, + @Vector(3, u8){ 2, 4, 6 }, + }, + .{}, + ); +} From ffbf9a03438b3a18f0606c97857826d37f9ad94a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 31 Jan 2025 21:59:01 -0800 Subject: [PATCH 77/98] Runtime parser now accepts pointers, allocates as needed --- lib/std/zon.zig | 3 + lib/std/zon/parse.zig | 247 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 232 insertions(+), 18 deletions(-) diff --git a/lib/std/zon.zig b/lib/std/zon.zig index 3f73b1936dd1..0c7294ab86ad 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -32,6 +32,9 @@ //! ``` //! //! ZON may not contain type names. +//! +//! ZON does not have syntax for pointers, but the parsers will allocate as needed to match the +//! given Zig types. Similarly, the serializer will traverse pointers. pub const parse = @import("zon/parse.zig"); pub const stringify = @import("zon/stringify.zig"); diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 0d5a57503633..a0faca8267e9 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -357,12 +357,18 @@ pub fn free(gpa: Allocator, value: anytype) void { .bool, .int, .float, .@"enum" => {}, .pointer => |pointer| { switch (pointer.size) { - .one, .many, .c => if (comptime requiresAllocator(Value)) unreachable, - .slice => for (value) |item| { - free(gpa, item); + .one => { + free(gpa, value.*); + gpa.destroy(value); }, + .slice => { + for (value) |item| { + free(gpa, item); + } + gpa.free(value); + }, + .many, .c => comptime unreachable, } - return gpa.free(value); }, .array => for (value) |item| { free(gpa, item); @@ -380,8 +386,8 @@ pub fn free(gpa: Allocator, value: anytype) void { .optional => if (value) |some| { free(gpa, some); }, + .vector => |vector| for (0..vector.len) |i| free(gpa, value[i]), .void => {}, - .null => {}, else => comptime unreachable, } } @@ -402,6 +408,7 @@ fn requiresAllocator(comptime T: type) bool { } } else false, .optional => |optional| requiresAllocator(optional.child), + .vector => false, else => false, }; } @@ -413,25 +420,81 @@ const Parser = struct { status: ?*Status, options: Options, + /// Given a Zig type, flattens it into a compatible ZON type. + /// + /// For example, `*u32` becomes `u32`, `*?*bool` becomes `?bool`. Does not recurse inside of + /// containers. + /// + /// This is necessary for parsing self referential types (i.e. any tree structure laid out in a + /// human readable form in a config file), and allows putting large optional data behind a + /// pointer. + fn ZonType(T: type) type { + var is_opt = false; + comptime var Zt = T; + while (true) { + switch (@typeInfo(Zt)) { + .pointer => |pointer| { + if (pointer.size != .one) break; + Zt = pointer.child; + }, + .optional => |optional| { + if (is_opt) { + @compileError("result type contains nested optionals; not representable in ZON"); + } + is_opt = true; + Zt = optional.child; + }, + else => break, + } + } + if (is_opt) Zt = ?Zt; + return Zt; + } + + /// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. + fn toZigVal(T: type, gpa: Allocator, val: anytype) !T { + if (@TypeOf(val) == T) return val; + switch (@typeInfo(T)) { + .optional => |optional| { + const inner = val orelse return null; + return try toZigVal(optional.child, gpa, inner); + }, + .pointer => |pointer| { + const result = try gpa.create(pointer.child); + result.* = try toZigVal(pointer.child, gpa, val); + return result; + }, + else => comptime unreachable, + } + } + fn parseExpr(self: *@This(), comptime T: type, 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, node), - .array => return self.parseArray(T, node), + + // Flatten the type into a Zon compatible type + const Zt = ZonType(T); + + // Parse the value into the flattened Zon type + const val: Zt = switch (@typeInfo(Zt)) { + .bool => try self.parseBool(node), + .int => try self.parseInt(Zt, node), + .float => try self.parseFloat(Zt, node), + .optional => try self.parseOptional(Zt, node), + .@"enum" => try self.parseEnumLiteral(Zt, node), + .pointer => try self.parsePointer(Zt, node), + .array => try self.parseArray(Zt, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) - return self.parseTuple(T, node) + try self.parseTuple(Zt, node) else - return self.parseStruct(T, node), - .@"union" => return self.parseUnion(T, node), - .optional => return self.parseOptional(T, node), - .vector => return self.parseVector(T, node), + try self.parseStruct(Zt, node), + .@"union" => try self.parseUnion(Zt, node), + .vector => try self.parseVector(Zt, node), else => comptime unreachable, - } + }; + + // Convert the result back to a Zig type + return toZigVal(T, self.gpa, val); } fn parseBool(self: @This(), node: Zoir.Node.Index) !bool { @@ -2909,6 +2972,26 @@ test "std.zon vector" { try fromSlice(@Vector(3, u8), gpa, ".{2, 4, 6}", null, .{}), ); + { + try std.testing.expectEqual( + @Vector(0, *const u8){}, + try fromSlice(@Vector(0, *const u8), gpa, ".{}", null, .{}), + ); + const pointers = try fromSlice(@Vector(3, *const u8), gpa, ".{2, 4, 6}", null, .{}); + defer free(gpa, pointers); + try std.testing.expectEqualDeep(@Vector(3, *const u8){ &2, &4, &6 }, pointers); + } + + { + try std.testing.expectEqual( + @Vector(0, ?*const u8){}, + try fromSlice(@Vector(0, ?*const u8), gpa, ".{}", null, .{}), + ); + const pointers = try fromSlice(@Vector(3, ?*const u8), gpa, ".{2, null, 6}", null, .{}); + defer free(gpa, pointers); + try std.testing.expectEqualDeep(@Vector(3, ?*const u8){ &2, null, &6 }, pointers); + } + // Too few fields { var status: Status = .{}; @@ -2954,3 +3037,131 @@ test "std.zon vector" { ); } } + +test "std.zon add pointers" { + const gpa = std.testing.allocator; + + // Primitive with varying levels of pointers + { + const result = try fromSlice(*u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*); + } + + { + const result = try fromSlice(**u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*.*); + } + + { + const result = try fromSlice(***u32, gpa, "10", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual(@as(u32, 10), result.*.*.*); + } + + // Primitive optional with varying levels of pointers + { + const some = try fromSlice(?*u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.?.*); + + const none = try fromSlice(?*u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none); + } + + { + const some = try fromSlice(*?u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.?); + + const none = try fromSlice(*?u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*); + } + + { + const some = try fromSlice(?**u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.?.*.*); + + const none = try fromSlice(?**u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none); + } + + { + const some = try fromSlice(*?*u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.?.*); + + const none = try fromSlice(*?*u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*); + } + + { + const some = try fromSlice(**?u32, gpa, "10", null, .{}); + defer free(gpa, some); + try std.testing.expectEqual(@as(u32, 10), some.*.*.?); + + const none = try fromSlice(**?u32, gpa, "null", null, .{}); + defer free(gpa, none); + try std.testing.expectEqual(null, none.*.*); + } + + // Pointer to an array + { + const result = try fromSlice(*[3]u8, gpa, ".{ 1, 2, 3 }", null, .{}); + defer free(gpa, result); + try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*); + } + + // Dereferencing is not allowed, potential foot-gun without a clear use case + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice([3]u8, gpa, "\"foo\"", &status, .{}), + ); + try std.testing.expectFmt( + "1:1: error: expected tuple\n", + "{}", + .{status}, + ); + } + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const expected: Outer = .{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + const found = try fromSlice(?*Outer, gpa, + \\.{ + \\ .f1 = .{ + \\ .f1 = null, + \\ .f2 = "foo", + \\ }, + \\ .f2 = null, + \\} + , null, .{}); + defer free(gpa, found); + + try std.testing.expectEqualDeep(expected, found.?.*); + } +} From bae7d34ecc22a223ead5a6fdd0c9f6be1f9f2e20 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 31 Jan 2025 22:35:07 -0800 Subject: [PATCH 78/98] Follows pointers when serializing --- lib/std/zon/stringify.zig | 77 +++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 522deb6501e7..505a94de3d67 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -381,21 +381,23 @@ pub fn Serializer(Writer: type) type { }, .void => try self.writer.writeAll("{}"), .pointer => |pointer| { - const child_type = switch (@typeInfo(pointer.child)) { + // Try to serialize as a string + const item: ?type = switch (@typeInfo(pointer.child)) { .array => |array| array.child, - else => b: { - comptime assert(pointer.size == .slice); - break :b pointer.child; - }, + else => if (pointer.size == .slice) pointer.child else null, }; - const sentinel = pointer.sentinel(); - if (child_type == u8 and - (sentinel == null or sentinel == 0) and + if (item == u8 and + (pointer.sentinel() == null or pointer.sentinel() == 0) and !options.emit_strings_as_containers) { - try self.string(val); - } else { - try self.tupleImpl(val, options); + return try self.string(val); + } + + // Serialize as either a tuple or as the child type + switch (pointer.size) { + .slice => try self.tupleImpl(val, options), + .one => try self.valueArbitraryDepth(val.*, options), + else => comptime unreachable, } }, .array => { @@ -2118,6 +2120,8 @@ test "std.zon stringify vector" { \\ 4, \\ 6, \\ }, + \\ .{ 1, 2 }, + \\ .{ 3, 4 }, \\} , .{ @@ -2127,7 +2131,58 @@ test "std.zon stringify vector" { @Vector(3, f32){ 1.5, 2.5, 3.5 }, @Vector(0, u8){}, @Vector(3, u8){ 2, 4, 6 }, + @Vector(2, *const u8){ &1, &2 }, + @Vector(2, ?*const u8){ &3, &4 }, }, .{}, ); } + +test "std.zon pointers" { + // Primitive with varying levels of pointers + try expectSerializeEqual("10", &@as(u32, 10), .{}); + try expectSerializeEqual("10", &&@as(u32, 10), .{}); + try expectSerializeEqual("10", &&&@as(u32, 10), .{}); + + // Primitive optional with varying levels of pointers + try expectSerializeEqual("10", @as(?*const u32, &10), .{}); + try expectSerializeEqual("null", @as(?*const u32, null), .{}); + try expectSerializeEqual("10", @as(?*const u32, &10), .{}); + try expectSerializeEqual("null", @as(*const ?u32, &null), .{}); + + try expectSerializeEqual("10", @as(?*const *const u32, &&10), .{}); + try expectSerializeEqual("null", @as(?*const *const u32, null), .{}); + try expectSerializeEqual("10", @as(*const ?*const u32, &&10), .{}); + try expectSerializeEqual("null", @as(*const ?*const u32, &null), .{}); + try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{}); + try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{}); + + // Nested optionals are fine when serializing + try expectSerializeEqual("10", @as(??u32, 10), .{}); + try expectSerializeEqual("null", @as(??u32, null), .{}); + + try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{}); + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const val: ?*const Outer = &.{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + try expectSerializeEqual( + \\.{ .f1 = .{ .f1 = null, .f2 = "foo" }, .f2 = null } + , val, .{}); + } +} From 75dc8cc2a402e28d338abcef152f7be93f54e677 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 31 Jan 2025 22:42:20 -0800 Subject: [PATCH 79/98] Updates requiresAllocator --- lib/std/zon/parse.zig | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index a0faca8267e9..7922386c1808 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -396,7 +396,7 @@ fn requiresAllocator(comptime T: type) bool { _ = valid_types; return switch (@typeInfo(T)) { .pointer => true, - .array => |array| requiresAllocator(array.child), + .array => |array| return array.len > 0 and requiresAllocator(array.child), .@"struct" => |@"struct"| inline for (@"struct".fields) |field| { if (requiresAllocator(field.type)) { break true; @@ -408,7 +408,7 @@ fn requiresAllocator(comptime T: type) bool { } } else false, .optional => |optional| requiresAllocator(optional.child), - .vector => false, + .vector => |vector| return vector.len > 0 and requiresAllocator(vector.child), else => false, }; } @@ -1125,12 +1125,15 @@ test "std.zon requiresAllocator" { try std.testing.expect(!requiresAllocator(enum { foo })); try std.testing.expect(!requiresAllocator(struct { f32 })); try std.testing.expect(!requiresAllocator(struct { x: f32 })); + try std.testing.expect(!requiresAllocator([0][]const u8)); try std.testing.expect(!requiresAllocator([2]u8)); try std.testing.expect(!requiresAllocator(union { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(union(enum) { x: f32, y: f32 })); try std.testing.expect(!requiresAllocator(?f32)); try std.testing.expect(!requiresAllocator(void)); try std.testing.expect(!requiresAllocator(@TypeOf(null))); + try std.testing.expect(!requiresAllocator(@Vector(3, u8))); + try std.testing.expect(!requiresAllocator(@Vector(0, *const u8))); try std.testing.expect(requiresAllocator([]u8)); try std.testing.expect(requiresAllocator(*struct { u8, u8 })); @@ -1139,6 +1142,7 @@ test "std.zon requiresAllocator" { try std.testing.expect(requiresAllocator(union { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(union(enum) { x: i32, y: []u8 })); try std.testing.expect(requiresAllocator(?[]u8)); + try std.testing.expect(requiresAllocator(@Vector(3, *const u8))); } test "std.zon ast errors" { From 6a2f3e2b95bc42c259cc9c7b5148089613f1a5f6 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 12:21:59 -0800 Subject: [PATCH 80/98] Starts to support adding pointers to comptime import of ZON (unfinished) Still needs: * To reject non const pointers * To reject types with nested optionals up front * To show the flattened types in error messages (at runtime too) --- lib/std/zon/parse.zig | 47 ++----- src/Sema/LowerZon.zig | 130 ++++++++++++++---- test/behavior/zon.zig | 69 ++++++++++ test/behavior/zon/complex.zon | 7 + test/behavior/zon/vec3_int_opt.zon | 1 + .../@import_zon_coerce_pointer.zig | 10 -- .../@import_zon_string_as_array.zig | 10 ++ test/cases/compile_errors/zon/hello.zon | 1 + 8 files changed, 209 insertions(+), 66 deletions(-) create mode 100644 test/behavior/zon/complex.zon create mode 100644 test/behavior/zon/vec3_int_opt.zon delete mode 100644 test/cases/compile_errors/@import_zon_coerce_pointer.zig create mode 100644 test/cases/compile_errors/@import_zon_string_as_array.zig create mode 100644 test/cases/compile_errors/zon/hello.zon diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 7922386c1808..80167e8ea557 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -422,12 +422,7 @@ const Parser = struct { /// Given a Zig type, flattens it into a compatible ZON type. /// - /// For example, `*u32` becomes `u32`, `*?*bool` becomes `?bool`. Does not recurse inside of - /// containers. - /// - /// This is necessary for parsing self referential types (i.e. any tree structure laid out in a - /// human readable form in a config file), and allows putting large optional data behind a - /// pointer. + /// See the equivalent function in `LowerZon`. fn ZonType(T: type) type { var is_opt = false; comptime var Zt = T; @@ -454,10 +449,11 @@ const Parser = struct { /// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. fn toZigVal(T: type, gpa: Allocator, val: anytype) !T { if (@TypeOf(val) == T) return val; + switch (@typeInfo(T)) { .optional => |optional| { const inner = val orelse return null; - return try toZigVal(optional.child, gpa, inner); + return toZigVal(optional.child, gpa, inner); }, .pointer => |pointer| { const result = try gpa.create(pointer.child); @@ -559,9 +555,9 @@ const Parser = struct { fn parsePointer(self: *@This(), comptime T: type, 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, nodes), - .empty_literal => return try self.parseSlice(T, .{ .start = node, .len = 0 }), + .string_literal => return self.parseString(T, node), + .array_literal => |nodes| return self.parseSlice(T, nodes), + .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }), else => return self.failExpectedContainer(T, node), } } @@ -593,9 +589,9 @@ const Parser = struct { } if (pointer.sentinel() != null) { - return try buf.toOwnedSliceSentinel(self.gpa, 0); + return buf.toOwnedSliceSentinel(self.gpa, 0); } else { - return try buf.toOwnedSlice(self.gpa); + return buf.toOwnedSlice(self.gpa); } } @@ -1065,7 +1061,7 @@ const Parser = struct { return self.failNode(node, "expected struct"); }, .@"union" => return self.failNode(node, "expected union"), - .array => return self.failNode(node, "expected tuple"), + .array => return self.failNode(node, "expected array"), .pointer => |pointer| { if (pointer.child == u8 and pointer.size == .slice and @@ -1074,8 +1070,10 @@ const Parser = struct { pointer.alignment == 1) { return self.failNode(node, "expected string"); - } else { + } else if (pointer.size == .slice) { return self.failNode(node, "expected tuple"); + } else { + return self.failExpectedContainer(pointer.child, node); } }, .vector => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), @@ -1877,7 +1875,7 @@ test "std.zon arrays and slices" { error.ParseZon, fromSlice([3]u8, gpa, "'a'", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } // Slice @@ -1968,7 +1966,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([4:0]u8, gpa, "\"abcd\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { @@ -1978,7 +1976,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([4:0]u8, gpa, "\\\\abcd", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -3122,21 +3120,6 @@ test "std.zon add pointers" { try std.testing.expectEqual([3]u8{ 1, 2, 3 }, result.*); } - // Dereferencing is not allowed, potential foot-gun without a clear use case - { - var status: Status = .{}; - defer status.deinit(gpa); - try std.testing.expectError( - error.ParseZon, - fromSlice([3]u8, gpa, "\"foo\"", &status, .{}), - ); - try std.testing.expectFmt( - "1:1: error: expected tuple\n", - "{}", - .{status}, - ); - } - // A complicated type with nested internal pointers and string allocations { const Inner = struct { diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index d16da755e11b..4614c654745c 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -4,6 +4,7 @@ const Sema = @import("../Sema.zig"); const Air = @import("../Air.zig"); const InternPool = @import("../InternPool.zig"); const Type = @import("../Type.zig"); +const Value = @import("../Value.zig"); const Zir = std.zig.Zir; const AstGen = std.zig.AstGen; const CompileError = Zcu.CompileError; @@ -75,20 +76,97 @@ fn fail( return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } +/// Given a Zig type, flattens it into a compatible ZON type. +/// +/// For example, `*u32` becomes `u32`, `*?*bool` becomes `?bool`. Does not recurse inside of +/// containers. +/// +/// This is necessary for parsing self referential types (i.e. any tree structure laid out in a +/// human readable form in a config file), and allows putting large optional data behind a +/// pointer. +fn zonType(self: *LowerZon, ty: Type) CompileError!Type { + const zcu = self.sema.pt.zcu; + var is_opt: bool = false; + var zt = ty; + while (true) { + switch (zt.zigTypeTag(zcu)) { + .optional => { + if (is_opt) { + @branchHint(.cold); + return self.sema.failWithOwnedErrorMsg(self.block, try Zcu.ErrorMsg.create( + zcu.gpa, + self.import_loc, + "result type contains nested optionals; not representable in ZON", + .{}, + )); + } + is_opt = true; + zt = zt.optionalChild(zcu); + }, + .pointer => { + if (!zt.isSinglePointer(zcu)) break; + zt = .fromInterned(zt.ptrInfo(zcu).child); + }, + else => break, + } + } + if (is_opt) zt = .fromInterned(try self.sema.pt.intern(.{ .opt_type = zt.toIntern() })); + return zt; +} + +/// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. +fn toZigVal(self: *LowerZon, res_ty: Type, val: Value) CompileError!InternPool.Index { + const pt = &self.sema.pt; + + if (val.typeOf(pt.zcu).eql(res_ty, pt.zcu)) { + return val.toIntern(); + } + + switch (res_ty.zigTypeTag(pt.zcu)) { + .optional => return pt.intern(.{ + .opt = .{ + .ty = res_ty.toIntern(), + .val = if (val.optionalValue(pt.zcu)) |inner| b: { + break :b try self.toZigVal(res_ty.optionalChild(pt.zcu), inner); + } else b: { + break :b .none; + }, + }, + }), + .pointer => return pt.intern(.{ + .ptr = .{ + .ty = res_ty.toIntern(), + .base_addr = .{ + .uav = .{ + .orig_ty = res_ty.toIntern(), + .val = try self.toZigVal(res_ty.childType(pt.zcu), val), + }, + }, + .byte_offset = 0, + }, + }), + else => unreachable, + } +} + fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { - switch (res_ty.zigTypeTag(self.sema.pt.zcu)) { - .bool => return self.lowerBool(node), - .int, .comptime_int => return self.lowerInt(node, res_ty), - .float, .comptime_float => return self.lowerFloat(node, res_ty), - .optional => return self.lowerOptional(node, res_ty), - .null => return self.lowerNull(node), - .@"enum" => return self.lowerEnum(node, res_ty), - .enum_literal => return self.lowerEnumLiteral(node, res_ty), - .array => return self.lowerArray(node, res_ty), - .@"struct" => return self.lowerStructOrTuple(node, res_ty), - .@"union" => return self.lowerUnion(node, res_ty), - .pointer => return self.lowerPointer(node, res_ty), - .vector => return self.lowerVector(node, res_ty), + // Flatten the result type into a compatible ZON type + const zt = try self.zonType(res_ty); + + // Parse to the stripped result type + const val = switch (zt.zigTypeTag(self.sema.pt.zcu)) { + .bool => try self.lowerBool(node), + .int, .comptime_int => try self.lowerInt(node, zt), + .float, .comptime_float => try self.lowerFloat(node, zt), + .optional => try self.lowerOptional(node, zt), + .null => try self.lowerNull(node), + .@"enum" => try self.lowerEnum(node, zt), + .enum_literal => try self.lowerEnumLiteral(node, zt), + .array => try self.lowerArray(node, zt), + .@"struct" => try self.lowerStructOrTuple(node, zt), + .@"union" => try self.lowerUnion(node, zt), + .pointer => try self.lowerPointer(node, zt), + .vector => try self.lowerVector(node, zt), .type, .noreturn, @@ -103,9 +181,11 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! => return self.fail( node, "type '{}' not available in ZON", - .{res_ty.fmt(self.sema.pt)}, + .{zt.fmt(self.sema.pt)}, ), - } + }; + + return self.toZigVal(res_ty, .fromInterned(val)); } fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { @@ -122,7 +202,6 @@ fn lowerInt( res_ty: Type, ) !InternPool.Index { @setFloatMode(.strict); - const gpa = self.sema.gpa; return switch (node.get(self.file.zoir.?)) { .int_literal => |int| switch (int) { .small => |val| { @@ -162,7 +241,7 @@ fn lowerInt( } } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + return self.sema.pt.intern(.{ .int = .{ .ty = res_ty.toIntern(), .storage = .{ .i64 = rhs }, } }); @@ -179,7 +258,7 @@ fn lowerInt( } } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ .int = .{ + return self.sema.pt.intern(.{ .int = .{ .ty = res_ty.toIntern(), .storage = .{ .big_int = val }, } }); @@ -217,7 +296,7 @@ fn lowerInt( ); } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + return self.sema.pt.intern(.{ .int = .{ .ty = res_ty.toIntern(), .storage = .{ .big_int = rational.p.toConst() }, @@ -240,7 +319,7 @@ fn lowerInt( } } } - return self.sema.pt.zcu.intern_pool.get(gpa, self.sema.pt.tid, .{ + return self.sema.pt.intern(.{ .int = .{ .ty = res_ty.toIntern(), .storage = .{ .i64 = val }, @@ -299,10 +378,13 @@ fn lowerFloat( } 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)), - }; + return self.sema.pt.intern(.{ .opt = .{ + .ty = res_ty.toIntern(), + .val = switch (node.get(self.file.zoir.?)) { + .null => .none, + else => try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), + }, + } }); } fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 8f3a5d87213a..3366588e383d 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -441,4 +441,73 @@ test "vector" { const expected: @Vector(3, u8) = .{ 2, 4, 6 }; try expectEqual(expected, actual); } + + { + const actual: @Vector(0, *const u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, *const u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, *const u8) = @import("zon/vec3_int.zon"); + const expected: @Vector(3, *const u8) = .{ &2, &4, &6 }; + try expectEqual(expected, actual); + } + + { + const actual: @Vector(0, ?*const u8) = @import("zon/vec0.zon"); + const expected: @Vector(0, ?*const u8) = .{}; + try expectEqual(expected, actual); + } + { + const actual: @Vector(3, ?*const u8) = @import("zon/vec3_int_opt.zon"); + const expected: @Vector(3, ?*const u8) = .{ &2, null, &6 }; + try expectEqual(expected, actual); + } +} + +test "pointers" { + // Primitive with varying levels of pointers + try expectEqual(@as(u8, 'a'), @as(*const u8, @import("zon/a.zon")).*); + try expectEqual(@as(u8, 'a'), @as(*const *const u8, @import("zon/a.zon")).*.*); + try expectEqual(@as(u8, 'a'), @as(*const *const *const u8, @import("zon/a.zon")).*.*.*); + + // Primitive optional with varying levels of pointers + try expectEqual(@as(u8, 'a'), @as(?*const u8, @import("zon/a.zon")).?.*); + try expectEqual(null, @as(?*const u8, @import("zon/none.zon"))); + + try expectEqual(@as(u8, 'a'), @as(*const ?u8, @import("zon/a.zon")).*.?); + try expectEqual(null, @as(*const ?u8, @import("zon/none.zon")).*); + + try expectEqual(@as(u8, 'a'), @as(?*const *const u8, @import("zon/a.zon")).?.*.*); + try expectEqual(null, @as(?*const *const u8, @import("zon/none.zon"))); + + try expectEqual(@as(u8, 'a'), @as(*const ?*const u8, @import("zon/a.zon")).*.?.*); + try expectEqual(null, @as(*const ?*const u8, @import("zon/none.zon")).*); + + try expectEqual(@as(u8, 'a'), @as(*const *const ?u8, @import("zon/a.zon")).*.*.?); + try expectEqual(null, @as(*const *const ?u8, @import("zon/none.zon")).*.*); + + try expectEqual([3]u8{ 2, 4, 6 }, @as(*const [3]u8, @import("zon/vec3_int.zon")).*); + + // A complicated type with nested internal pointers and string allocations + { + const Inner = struct { + f1: *const ?*const []const u8, + f2: *const ?*const []const u8, + }; + const Outer = struct { + f1: *const ?*const Inner, + f2: *const ?*const Inner, + }; + const expected: Outer = .{ + .f1 = &&.{ + .f1 = &null, + .f2 = &&"foo", + }, + .f2 = &null, + }; + + const found: ?*const Outer = @import("zon/complex.zon"); + try std.testing.expectEqualDeep(expected, found.?.*); + } } diff --git a/test/behavior/zon/complex.zon b/test/behavior/zon/complex.zon new file mode 100644 index 000000000000..0d61c25517e1 --- /dev/null +++ b/test/behavior/zon/complex.zon @@ -0,0 +1,7 @@ +.{ + .f1 = .{ + .f1 = null, + .f2 = "foo", + }, + .f2 = null, +} diff --git a/test/behavior/zon/vec3_int_opt.zon b/test/behavior/zon/vec3_int_opt.zon new file mode 100644 index 000000000000..9c009b99d6b2 --- /dev/null +++ b/test/behavior/zon/vec3_int_opt.zon @@ -0,0 +1 @@ +.{ 2, null, 6 } diff --git a/test/cases/compile_errors/@import_zon_coerce_pointer.zig b/test/cases/compile_errors/@import_zon_coerce_pointer.zig deleted file mode 100644 index 9cbae754045a..000000000000 --- a/test/cases/compile_errors/@import_zon_coerce_pointer.zig +++ /dev/null @@ -1,10 +0,0 @@ -export fn entry() void { - const f: *struct { u8, u8, u8 } = @import("zon/array.zon"); - _ = f; -} - -// error -// imports=zon/array.zon -// -// array.zon:1:2: error: non slice pointers are not available in ZON -// tmp.zig:2:47: note: imported here diff --git a/test/cases/compile_errors/@import_zon_string_as_array.zig b/test/cases/compile_errors/@import_zon_string_as_array.zig new file mode 100644 index 000000000000..996f239bc46a --- /dev/null +++ b/test/cases/compile_errors/@import_zon_string_as_array.zig @@ -0,0 +1,10 @@ +export fn entry() void { + const f: [5]u8 = @import("zon/hello.zon"); + _ = f; +} + +// error +// imports=zon/hello.zon +// +// hello.zon:1:1: error: expected type '[5]u8' +// tmp.zig:2:30: note: imported here diff --git a/test/cases/compile_errors/zon/hello.zon b/test/cases/compile_errors/zon/hello.zon new file mode 100644 index 000000000000..3580093b9da0 --- /dev/null +++ b/test/cases/compile_errors/zon/hello.zon @@ -0,0 +1 @@ +"hello" From c67cd7e14730b4c166e9b277507f8b9a3258603a Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 13:54:18 -0800 Subject: [PATCH 81/98] Correctly shows flattened types in runtime parser errors --- lib/std/zon/parse.zig | 349 ++++++++++++++++++++++++++++++++---------- 1 file changed, 268 insertions(+), 81 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 80167e8ea557..9acec68cb6f4 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -464,40 +464,120 @@ const Parser = struct { } } - fn parseExpr(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseExpr( + self: *@This(), + comptime T: type, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory }!T { _ = valid_types; // Flatten the type into a Zon compatible type - const Zt = ZonType(T); - - // Parse the value into the flattened Zon type - const val: Zt = switch (@typeInfo(Zt)) { - .bool => try self.parseBool(node), - .int => try self.parseInt(Zt, node), - .float => try self.parseFloat(Zt, node), - .optional => try self.parseOptional(Zt, node), - .@"enum" => try self.parseEnumLiteral(Zt, node), - .pointer => try self.parsePointer(Zt, node), - .array => try self.parseArray(Zt, node), + const Flattened = ZonType(T); + + // If we're optional, check for null or get the child type + const Child = switch (@typeInfo(Flattened)) { + .optional => |optional| if (node.get(self.zoir) == .null) { + return toZigVal(T, self.gpa, null); + } else optional.child, + else => Flattened, + }; + + // Otherwise, parse the value into the flattened Zon type + const val: Flattened = switch (@typeInfo(Child)) { + .bool => self.parseBool(node), + .int => self.parseInt(Child, node), + .float => self.parseFloat(Child, node), + .@"enum" => self.parseEnumLiteral(Child, node), + .pointer => self.parsePointer(Child, node), + .array => self.parseArray(Child, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) - try self.parseTuple(Zt, node) + self.parseTuple(Child, node) else - try self.parseStruct(Zt, node), - .@"union" => try self.parseUnion(Zt, node), - .vector => try self.parseVector(Zt, node), + self.parseStruct(Child, node), + .@"union" => self.parseUnion(Child, node), + .vector => self.parseVector(Child, node), else => comptime unreachable, + } catch |err| switch (err) { + error.WrongType => return self.failExpectedType(Flattened, node), + else => |e| return e, }; // Convert the result back to a Zig type return toZigVal(T, self.gpa, val); } + fn failExpectedType(self: @This(), Zt: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory } { + @branchHint(.cold); + + const T, const is_opt = switch (@typeInfo(Zt)) { + .optional => |optional| .{ optional.child, true }, + else => .{ Zt, false }, + }; + + _ = valid_types; + + switch (@typeInfo(T)) { + .@"struct" => |@"struct"| if (@"struct".is_tuple) { + if (is_opt) { + return self.failNode(node, "expected optional tuple"); + } else { + return self.failNode(node, "expected tuple"); + } + } else { + if (is_opt) { + return self.failNode(node, "expected optional struct"); + } else { + return self.failNode(node, "expected struct"); + } + }, + .@"union" => if (is_opt) { + return self.failNode(node, "expected optional union"); + } else { + return self.failNode(node, "expected union"); + }, + .array => if (is_opt) { + return self.failNode(node, "expected optional array"); + } else { + return self.failNode(node, "expected array"); + }, + .pointer => |pointer| { + if (pointer.child == u8 and + pointer.size == .slice and + pointer.is_const and + (pointer.sentinel() == null or pointer.sentinel() == 0) and + pointer.alignment == 1) + { + if (is_opt) { + return self.failNode(node, "expected optional string"); + } else { + return self.failNode(node, "expected string"); + } + } else if (pointer.size == .slice) { + if (is_opt) { + return self.failNode(node, "expected optional array"); + } else { + return self.failNode(node, "expected array"); + } + } else comptime unreachable; + }, + .vector, .bool, .int, .float => { + return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(Zt)}); + }, + .@"enum" => if (is_opt) { + return self.failNode(node, "expected optional enum literal"); + } else { + return self.failNode(node, "expected enum literal"); + }, + else => @compileError(@typeName(Zt)), + } + } + 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'"), + else => return error.WrongType, } } @@ -514,7 +594,7 @@ const Parser = struct { .char_literal => |val| return std.math.cast(T, val) orelse self.failCannotRepresent(T, node), - else => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), + else => return error.WrongType, } } @@ -529,7 +609,7 @@ const Parser = struct { .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)}), + else => return error.WrongType, } } @@ -549,7 +629,7 @@ const Parser = struct { return enum_tags.get(field_name_str) orelse self.failUnexpected(T, "enum literal", node, null, field_name_str); }, - else => return self.failNode(node, "expected enum literal"), + else => return error.WrongType, } } @@ -558,7 +638,7 @@ const Parser = struct { .string_literal => return self.parseString(T, node), .array_literal => |nodes| return self.parseSlice(T, nodes), .empty_literal => return self.parseSlice(T, .{ .start = node, .len = 0 }), - else => return self.failExpectedContainer(T, node), + else => return error.WrongType, } } @@ -585,7 +665,7 @@ const Parser = struct { (pointer.sentinel() != null and pointer.sentinel() != 0) or pointer.alignment != 1) { - return self.failExpectedContainer(T, node); + return error.WrongType; } if (pointer.sentinel() != null) { @@ -630,7 +710,7 @@ const Parser = struct { 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), + else => return error.WrongType, }; const array_info = @typeInfo(T).array; @@ -670,7 +750,7 @@ const Parser = struct { const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, - else => return self.failExpectedContainer(T, node), + else => return error.WrongType, }; const field_infos = @typeInfo(T).@"struct".fields; @@ -759,7 +839,7 @@ const Parser = struct { 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), + else => return error.WrongType, }; var result: T = undefined; @@ -822,7 +902,7 @@ const Parser = struct { .enum_literal => |field_name| { // The union must be tagged for an enum literal to coerce to it if (@"union".tag_type == null) { - return self.failNode(node, "expected union"); + return error.WrongType; } // Get the index of the named field. We don't use `parseEnum` here as @@ -848,7 +928,7 @@ const Parser = struct { }, .struct_literal => |struct_fields| { if (struct_fields.names.len != 1) { - return self.failNode(node, "expected union"); + return error.WrongType; } // Fill in the field we found @@ -870,18 +950,10 @@ const Parser = struct { else => unreachable, // Can't be out of bounds } }, - else => return self.failNode(node, "expected union"), + else => return error.WrongType, } } - fn parseOptional(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { - if (node.get(self.zoir) == .null) { - return null; - } - - return try self.parseExpr(@typeInfo(T).optional.child, node); - } - fn parseVector( self: *@This(), comptime T: type, @@ -892,7 +964,7 @@ const Parser = struct { 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), + else => return error.WrongType, }; var result: T = undefined; @@ -932,6 +1004,7 @@ const Parser = struct { note: ?Error.TypeCheckFailure.Note, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); + comptime assert(args.len > 0); if (self.status) |s| s.type_check = .{ .token = token, .offset = offset, @@ -1048,40 +1121,6 @@ const Parser = struct { } } - 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 array"), - .pointer => |pointer| { - if (pointer.child == u8 and - pointer.size == .slice and - pointer.is_const and - (pointer.sentinel() == null or pointer.sentinel() == 0) and - pointer.alignment == 1) - { - return self.failNode(node, "expected string"); - } else if (pointer.size == .slice) { - return self.failNode(node, "expected tuple"); - } else { - return self.failExpectedContainer(pointer.child, node); - } - }, - .vector => return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}), - else => {}, - } - comptime unreachable; - } - // Technically we could do this if we were willing to do a deep equal to verify // the value matched, but doing so doesn't seem to support any real use cases // so isn't worth the complexity at the moment. @@ -1101,7 +1140,13 @@ const Parser = struct { const value_node = array_init.ast.elements[field]; break :b self.ast.firstToken(value_node); }; - return self.failTokenFmt(token, 0, "cannot initialize comptime field", .{}); + return self.failToken(.{ + .token = token, + .offset = 0, + .message = "cannot initialize comptime field", + .owned = false, + .note = null, + }); } }; @@ -1886,7 +1931,7 @@ test "std.zon arrays and slices" { error.ParseZon, fromSlice([]u8, gpa, "'a'", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -1939,7 +1984,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]u8, gpa, "\"abcd\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { @@ -1949,7 +1994,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]u8, gpa, "\\\\abcd", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -2018,7 +2063,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([:1]const u8, gpa, "\"foo\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { @@ -2028,7 +2073,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([:1]const u8, gpa, "\\\\foo", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -2074,7 +2119,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]const i8, gpa, "\"a\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { @@ -2084,7 +2129,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]const i8, gpa, "\\\\a", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -2097,7 +2142,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]align(2) const u8, gpa, "\"abc\"", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } { @@ -2107,7 +2152,7 @@ test "std.zon string literal" { error.ParseZon, fromSlice([]align(2) const u8, gpa, "\\\\abc", &status, .{}), ); - try std.testing.expectFmt("1:1: error: expected tuple\n", "{}", .{status}); + try std.testing.expectFmt("1:1: error: expected array\n", "{}", .{status}); } } @@ -3038,6 +3083,17 @@ test "std.zon vector" { .{status}, ); } + + // Wrong type + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, u8), gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{}", .{status}); + } } test "std.zon add pointers" { @@ -3151,4 +3207,135 @@ test "std.zon add pointers" { try std.testing.expectEqualDeep(expected, found.?.*); } + + // Test that optional types are flattened correctly in errors + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?u8'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const f32, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?f32'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const @Vector(3, u8), gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?@Vector(3, u8)'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const bool, gpa, "10", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected type '?bool'\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const struct { a: i32 }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional struct\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const struct { i32 }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional tuple\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const union { x: void }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional union\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const [3]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(?[3]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const []u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(?[]u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional array\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const []const u8, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional string\n", "{}", .{status}); + } + + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(*const ?*const enum { foo }, gpa, "true", &status, .{}), + ); + try std.testing.expectFmt("1:1: error: expected optional enum literal\n", "{}", .{status}); + } } From 53aaee282a957fa0ac568fea69200ddab25db0b1 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 14:49:51 -0800 Subject: [PATCH 82/98] Correctly shows optionals in errors in LowerZon --- src/Sema/LowerZon.zig | 128 ++++++++++-------- .../compile_errors/@import_zon_opt_in_err.zig | 89 ++++++++++++ .../@import_zon_opt_in_err_struct.zig | 19 +++ .../@import_zon_vec_too_few.zig | 2 +- .../@import_zon_vec_too_many.zig | 2 +- .../@import_zon_vec_wrong_type.zig | 2 +- 6 files changed, 183 insertions(+), 59 deletions(-) create mode 100644 test/cases/compile_errors/@import_zon_opt_in_err.zig create mode 100644 test/cases/compile_errors/@import_zon_opt_in_err_struct.zig diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 4614c654745c..8f1d3e0e2242 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -116,7 +116,7 @@ fn zonType(self: *LowerZon, ty: Type) CompileError!Type { /// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. fn toZigVal(self: *LowerZon, res_ty: Type, val: Value) CompileError!InternPool.Index { - const pt = &self.sema.pt; + const pt = self.sema.pt; if (val.typeOf(pt.zcu).eql(res_ty, pt.zcu)) { return val.toIntern(); @@ -150,23 +150,36 @@ fn toZigVal(self: *LowerZon, res_ty: Type, val: Value) CompileError!InternPool.I } fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { + const pt = self.sema.pt; + // Flatten the result type into a compatible ZON type - const zt = try self.zonType(res_ty); - - // Parse to the stripped result type - const val = switch (zt.zigTypeTag(self.sema.pt.zcu)) { - .bool => try self.lowerBool(node), - .int, .comptime_int => try self.lowerInt(node, zt), - .float, .comptime_float => try self.lowerFloat(node, zt), - .optional => try self.lowerOptional(node, zt), - .null => try self.lowerNull(node), - .@"enum" => try self.lowerEnum(node, zt), - .enum_literal => try self.lowerEnumLiteral(node, zt), - .array => try self.lowerArray(node, zt), - .@"struct" => try self.lowerStructOrTuple(node, zt), - .@"union" => try self.lowerUnion(node, zt), - .pointer => try self.lowerPointer(node, zt), - .vector => try self.lowerVector(node, zt), + const flattened_ty = try self.zonType(res_ty); + + // If we're optional, check for null or get the child type + const child_type: Type = switch (pt.zcu.intern_pool.indexToKey(flattened_ty.toIntern())) { + .opt_type => |child_type| if (node.get(self.file.zoir.?) == .null) { + const none = try pt.intern(.{ .opt = .{ + .ty = flattened_ty.toIntern(), + .val = .none, + } }); + return self.toZigVal(res_ty, .fromInterned(none)); + } else .fromInterned(child_type), + else => flattened_ty, + }; + + // Parse to the child type + const child_val = switch (child_type.zigTypeTag(pt.zcu)) { + .bool => self.lowerBool(node), + .int, .comptime_int => self.lowerInt(node, child_type), + .float, .comptime_float => self.lowerFloat(node, child_type), + .null => self.lowerNull(node), + .@"enum" => self.lowerEnum(node, child_type), + .enum_literal => self.lowerEnumLiteral(node), + .array => self.lowerArray(node, child_type), + .@"struct" => self.lowerStructOrTuple(node, child_type), + .@"union" => self.lowerUnion(node, child_type), + .pointer => self.lowerSlice(node, child_type), + .vector => self.lowerVector(node, child_type), .type, .noreturn, @@ -178,21 +191,40 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! .frame, .@"anyframe", .void, - => return self.fail( + => return self.fail(node, "type '{}' not available in ZON", .{child_type.fmt(pt)}), + + .optional => { + // We unwrapped optional types above, and `zonType` never returns double optionals since + // they aren't allowed in ZON + unreachable; + }, + } catch |err| switch (err) { + error.WrongType => return self.fail( node, - "type '{}' not available in ZON", - .{zt.fmt(self.sema.pt)}, + "expected type '{}'", + .{flattened_ty.fmt(self.sema.pt)}, ), + else => |e| return e, + }; + + // Wrap the child type if an optional if necessary + const flattened_val = switch (flattened_ty.zigTypeTag(pt.zcu)) { + .optional => try pt.intern(.{ .opt = .{ + .ty = flattened_ty.toIntern(), + .val = child_val, + } }), + else => child_val, }; - return self.toZigVal(res_ty, .fromInterned(val)); + // Convert back to the Zig type + return self.toZigVal(res_ty, .fromInterned(flattened_val)); } 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, "expected type 'bool'", .{}), + else => return error.WrongType, }; } @@ -327,11 +359,7 @@ fn lowerInt( }); }, - else => return self.fail( - node, - "expected type '{}'", - .{res_ty.fmt(self.sema.pt)}, - ), + else => return error.WrongType, }; } @@ -372,25 +400,15 @@ fn lowerFloat( ); break :b try self.sema.pt.floatValue(res_ty, std.math.nan(f128)); }, - else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; return value.toIntern(); } -fn lowerOptional(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { - return self.sema.pt.intern(.{ .opt = .{ - .ty = res_ty.toIntern(), - .val = switch (node.get(self.file.zoir.?)) { - .null => .none, - else => try self.lowerExpr(node, res_ty.optionalChild(self.sema.pt.zcu)), - }, - } }); -} - fn lowerNull(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { switch (node.get(self.file.zoir.?)) { .null => return .null_value, - else => return self.fail(node, "expected null", .{}), + else => return error.WrongType, } } @@ -399,11 +417,11 @@ fn lowerArray(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. 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, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; if (nodes.len != array_info.len) { - return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return error.WrongType; } const elems = try self.sema.arena.alloc( @@ -450,11 +468,11 @@ fn lowerEnum(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.I return value.toIntern(); }, - else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, } } -fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; switch (node.get(self.file.zoir.?)) { .enum_literal => |field_name| { @@ -466,7 +484,7 @@ fn lowerEnumLiteral(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !Inter ); return self.sema.pt.intern(.{ .enum_literal = field_name_interned }); }, - else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, } } @@ -487,7 +505,7 @@ fn lowerTuple(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. 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, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; const field_types = tuple_info.types.get(ip); @@ -550,7 +568,7 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool const fields: @FieldType(Zoir.Node, "struct_literal") = switch (node.get(self.file.zoir.?)) { .struct_literal => |fields| fields, .empty_literal => .{ .names = &.{}, .vals = .{ .start = node, .len = 0 } }, - else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; const field_values = try self.sema.arena.alloc(InternPool.Index, struct_info.field_names.len); @@ -604,15 +622,13 @@ fn lowerStruct(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool } }); } -fn lowerPointer(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { +fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool.Index { const ip = &self.sema.pt.zcu.intern_pool; const gpa = self.sema.gpa; const ptr_info = res_ty.ptrInfo(self.sema.pt.zcu); - if (ptr_info.flags.size != .slice) { - return self.fail(node, "non slice pointers are not available in ZON", .{}); - } + assert(ptr_info.flags.size == .slice); // String literals const string_alignment = ptr_info.flags.alignment == .none or ptr_info.flags.alignment == .@"1"; @@ -637,7 +653,7 @@ fn lowerPointer(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPoo 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, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; const elems = try self.sema.arena.alloc(InternPool.Index, elem_nodes.len + @intFromBool(ptr_info.sentinel != .none)); @@ -716,7 +732,7 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), }; if (fields.names.len != 1) { - return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return error.WrongType; } const field_name = try ip.getOrPutString( self.sema.gpa, @@ -726,11 +742,11 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. ); break :b .{ field_name, fields.vals.at(0) }; }, - else => return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; const name_index = enum_tag_info.nameIndex(ip, field_name) orelse { - return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return error.WrongType; }; const tag = try self.sema.pt.enumValueFieldIndex(.fromInterned(union_info.enum_tag_ty), name_index); const field_type: Type = .fromInterned(union_info.field_types.get(ip)[name_index]); @@ -741,7 +757,7 @@ fn lowerUnion(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. break :b try self.lowerExpr(field_node, field_type); } else b: { if (field_type.toIntern() != .void_type) { - return self.fail(node, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}); + return error.WrongType; } break :b .void_value; }; @@ -760,7 +776,7 @@ fn lowerVector(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool 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, "expected type '{}'", .{res_ty.fmt(self.sema.pt)}), + else => return error.WrongType, }; const elems = try self.sema.arena.alloc(InternPool.Index, vector_info.len); diff --git a/test/cases/compile_errors/@import_zon_opt_in_err.zig b/test/cases/compile_errors/@import_zon_opt_in_err.zig new file mode 100644 index 000000000000..d38bb458063c --- /dev/null +++ b/test/cases/compile_errors/@import_zon_opt_in_err.zig @@ -0,0 +1,89 @@ +export fn testFloatA() void { + const f: ?f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testFloatB() void { + const f: *?f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testFloatC() void { + const f: ?*f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testFloatD() void { + const f: ?*f32 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testBool() void { + const f: ?bool = @import("zon/vec2.zon"); + _ = f; +} + +export fn testInt() void { + const f: ?i32 = @import("zon/vec2.zon"); + _ = f; +} + +const Enum = enum { foo }; +export fn testEnum() void { + const f: ?Enum = @import("zon/vec2.zon"); + _ = f; +} + +export fn testEnumLit() void { + const f: ?@TypeOf(.foo) = @import("zon/vec2.zon"); + _ = f; +} + +export fn testArray() void { + const f: ?[1]u8 = @import("zon/vec2.zon"); + _ = f; +} + +const Union = union {}; +export fn testUnion() void { + const f: ?Union = @import("zon/vec2.zon"); + _ = f; +} + +export fn testSlice() void { + const f: ?[]const u8 = @import("zon/vec2.zon"); + _ = f; +} + +export fn testVector() void { + const f: ?@Vector(3, f32) = @import("zon/vec2.zon"); + _ = f; +} + +// error +// imports=zon/vec2.zon +// +//vec2.zon:1:2: error: expected type '?f32' +//tmp.zig:2:29: note: imported here +//vec2.zon:1:2: error: expected type '?f32' +//tmp.zig:7:30: note: imported here +//vec2.zon:1:2: error: expected type '?f32' +//tmp.zig:12:30: note: imported here +//vec2.zon:1:2: error: expected type '?f32' +//tmp.zig:17:30: note: imported here +//vec2.zon:1:2: error: expected type '?bool' +//tmp.zig:22:30: note: imported here +//vec2.zon:1:2: error: expected type '?i32' +//tmp.zig:27:29: note: imported here +//vec2.zon:1:2: error: expected type '?tmp.Enum' +//tmp.zig:33:30: note: imported here +//vec2.zon:1:2: error: expected type '?@Type(.enum_literal)' +//tmp.zig:38:39: note: imported here +//vec2.zon:1:2: error: expected type '?[1]u8' +//tmp.zig:43:31: note: imported here +//vec2.zon:1:2: error: expected type '?tmp.Union' +//tmp.zig:49:31: note: imported here +//vec2.zon:1:2: error: expected type '?[]const u8' +//tmp.zig:54:36: note: imported here +//vec2.zon:1:2: error: expected type '?@Vector(3, f32)' +//tmp.zig:59:41: note: imported here diff --git a/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig b/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig new file mode 100644 index 000000000000..a284fd7c6dd6 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_opt_in_err_struct.zig @@ -0,0 +1,19 @@ +const Struct = struct { f: bool }; +export fn testStruct() void { + const f: ?Struct = @import("zon/nan.zon"); + _ = f; +} + +const Tuple = struct { bool }; +export fn testTuple() void { + const f: ?Tuple = @import("zon/nan.zon"); + _ = f; +} + +// error +// imports=zon/nan.zon +// +//nan.zon:1:1: error: expected type '?tmp.Struct' +//tmp.zig:3:32: note: imported here +//nan.zon:1:1: error: expected type '?struct { bool }' +//tmp.zig:9:31: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_too_few.zig b/test/cases/compile_errors/@import_zon_vec_too_few.zig index a44e3886c5e5..a55ce9ca6258 100644 --- a/test/cases/compile_errors/@import_zon_vec_too_few.zig +++ b/test/cases/compile_errors/@import_zon_vec_too_few.zig @@ -6,5 +6,5 @@ export fn entry() void { // error // imports=zon/tuple.zon // -// zon/tuple.zon:1:2: error: expected 3 vector elements; found 2 +// tuple.zon:1:2: error: expected 3 vector elements; found 2 // tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_too_many.zig b/test/cases/compile_errors/@import_zon_vec_too_many.zig index 8e55c8c4051c..321c26cf0a2f 100644 --- a/test/cases/compile_errors/@import_zon_vec_too_many.zig +++ b/test/cases/compile_errors/@import_zon_vec_too_many.zig @@ -6,5 +6,5 @@ export fn entry() void { // error // imports=zon/tuple.zon // -// zon/tuple.zon:1:2: error: expected 1 vector elements; found 2 +// tuple.zon:1:2: error: expected 1 vector elements; found 2 // tmp.zig:2:40: note: imported here diff --git a/test/cases/compile_errors/@import_zon_vec_wrong_type.zig b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig index 24b4801f2803..ce51dd7101a8 100644 --- a/test/cases/compile_errors/@import_zon_vec_wrong_type.zig +++ b/test/cases/compile_errors/@import_zon_vec_wrong_type.zig @@ -6,5 +6,5 @@ export fn entry() void { // error // imports=zon/tuple.zon // -// zon/tuple.zon:1:4: error: expected type 'bool' +// tuple.zon:1:4: error: expected type 'bool' // tmp.zig:2:41: note: imported here From d6231ec87254678a03d106b2ba387afac5851fdc Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 16:49:19 -0800 Subject: [PATCH 83/98] Simplifies pointer logic in runtime parser --- lib/std/zon/parse.zig | 232 ++++++++++++++++++++---------------------- 1 file changed, 108 insertions(+), 124 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 9acec68cb6f4..956935c7af0c 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -278,7 +278,7 @@ pub fn fromSlice( /// * unions /// * optionals /// * null - comptime T: type, + T: type, gpa: Allocator, source: [:0]const u8, status: ?*Status, @@ -301,7 +301,7 @@ pub fn fromSlice( /// Like `fromSlice`, but operates on `Zoir` instead of ZON source. pub fn fromZoir( - comptime T: type, + T: type, gpa: Allocator, ast: Ast, zoir: Zoir, @@ -313,7 +313,7 @@ pub fn fromZoir( /// Like `fromZoir`, but the parse starts on `node` instead of root. pub fn fromZoirNode( - comptime T: type, + T: type, gpa: Allocator, ast: Ast, zoir: Zoir, @@ -392,7 +392,7 @@ pub fn free(gpa: Allocator, value: anytype) void { } } -fn requiresAllocator(comptime T: type) bool { +fn requiresAllocator(T: type) bool { _ = valid_types; return switch (@typeInfo(T)) { .pointer => true, @@ -420,156 +420,128 @@ const Parser = struct { status: ?*Status, options: Options, - /// Given a Zig type, flattens it into a compatible ZON type. - /// - /// See the equivalent function in `LowerZon`. - fn ZonType(T: type) type { - var is_opt = false; - comptime var Zt = T; - while (true) { - switch (@typeInfo(Zt)) { - .pointer => |pointer| { - if (pointer.size != .one) break; - Zt = pointer.child; - }, - .optional => |optional| { - if (is_opt) { - @compileError("result type contains nested optionals; not representable in ZON"); - } - is_opt = true; - Zt = optional.child; - }, - else => break, - } - } - if (is_opt) Zt = ?Zt; - return Zt; + fn parseExpr(self: *@This(), T: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory }!T { + return self.parseExprInner(T, node) catch |err| switch (err) { + error.WrongType => return self.failExpectedType(T, node), + else => |e| return e, + }; } - /// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. - fn toZigVal(T: type, gpa: Allocator, val: anytype) !T { - if (@TypeOf(val) == T) return val; - + fn parseExprInner( + self: *@This(), + T: type, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory, WrongType }!T { switch (@typeInfo(T)) { - .optional => |optional| { - const inner = val orelse return null; - return toZigVal(optional.child, gpa, inner); + .optional => |optional| if (node.get(self.zoir) == .null) { + return null; + } else { + return try self.parseExprInner(optional.child, node); }, - .pointer => |pointer| { - const result = try gpa.create(pointer.child); - result.* = try toZigVal(pointer.child, gpa, val); + .bool => return self.parseBool(node), + .int => return self.parseInt(T, node), + .float => return self.parseFloat(T, node), + .@"enum" => return self.parseEnumLiteral(T, node), + .pointer => |pointer| if (pointer.size == .one) { + const result = try self.gpa.create(pointer.child); + errdefer self.gpa.destroy(result); + result.* = try self.parseExprInner(pointer.child, node); return result; + } else { + return self.parsePointer(T, node); }, - else => comptime unreachable, - } - } - - fn parseExpr( - self: *@This(), - comptime T: type, - node: Zoir.Node.Index, - ) error{ ParseZon, OutOfMemory }!T { - _ = valid_types; - - // Flatten the type into a Zon compatible type - const Flattened = ZonType(T); - - // If we're optional, check for null or get the child type - const Child = switch (@typeInfo(Flattened)) { - .optional => |optional| if (node.get(self.zoir) == .null) { - return toZigVal(T, self.gpa, null); - } else optional.child, - else => Flattened, - }; - - // Otherwise, parse the value into the flattened Zon type - const val: Flattened = switch (@typeInfo(Child)) { - .bool => self.parseBool(node), - .int => self.parseInt(Child, node), - .float => self.parseFloat(Child, node), - .@"enum" => self.parseEnumLiteral(Child, node), - .pointer => self.parsePointer(Child, node), - .array => self.parseArray(Child, node), + .array => return self.parseArray(T, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) - self.parseTuple(Child, node) + return self.parseTuple(T, node) else - self.parseStruct(Child, node), - .@"union" => self.parseUnion(Child, node), - .vector => self.parseVector(Child, node), + return self.parseStruct(T, node), + .@"union" => return self.parseUnion(T, node), + .vector => return self.parseVector(T, node), else => comptime unreachable, - } catch |err| switch (err) { - error.WrongType => return self.failExpectedType(Flattened, node), - else => |e| return e, - }; - - // Convert the result back to a Zig type - return toZigVal(T, self.gpa, val); + } } - fn failExpectedType(self: @This(), Zt: type, node: Zoir.Node.Index) error{ ParseZon, OutOfMemory } { + /// Prints a message of the form `expected T` where T is first converted to a ZON type. For + /// example, `**?**u8` becomes `?u8`, and types that involve user specified type names are just + /// referred to by the type of container. + fn failExpectedType( + self: @This(), + T: type, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory } { @branchHint(.cold); + return self.failExpectedTypeInner(T, false, node); + } - const T, const is_opt = switch (@typeInfo(Zt)) { - .optional => |optional| .{ optional.child, true }, - else => .{ Zt, false }, - }; - + fn failExpectedTypeInner( + self: @This(), + T: type, + opt: bool, + node: Zoir.Node.Index, + ) error{ ParseZon, OutOfMemory } { _ = valid_types; - switch (@typeInfo(T)) { .@"struct" => |@"struct"| if (@"struct".is_tuple) { - if (is_opt) { + if (opt) { return self.failNode(node, "expected optional tuple"); } else { return self.failNode(node, "expected tuple"); } } else { - if (is_opt) { + if (opt) { return self.failNode(node, "expected optional struct"); } else { return self.failNode(node, "expected struct"); } }, - .@"union" => if (is_opt) { + .@"union" => if (opt) { return self.failNode(node, "expected optional union"); } else { return self.failNode(node, "expected union"); }, - .array => if (is_opt) { + .array => if (opt) { return self.failNode(node, "expected optional array"); } else { return self.failNode(node, "expected array"); }, - .pointer => |pointer| { - if (pointer.child == u8 and - pointer.size == .slice and - pointer.is_const and - (pointer.sentinel() == null or pointer.sentinel() == 0) and - pointer.alignment == 1) - { - if (is_opt) { - return self.failNode(node, "expected optional string"); - } else { - return self.failNode(node, "expected string"); - } - } else if (pointer.size == .slice) { - if (is_opt) { - return self.failNode(node, "expected optional array"); + .pointer => |pointer| switch (pointer.size) { + .one => return self.failExpectedTypeInner(pointer.child, opt, node), + .slice => { + if (pointer.child == u8 and + pointer.is_const and + (pointer.sentinel() == null or pointer.sentinel() == 0) and + pointer.alignment == 1) + { + if (opt) { + return self.failNode(node, "expected optional string"); + } else { + return self.failNode(node, "expected string"); + } } else { - return self.failNode(node, "expected array"); + if (opt) { + return self.failNode(node, "expected optional array"); + } else { + return self.failNode(node, "expected array"); + } } - } else comptime unreachable; + }, + else => comptime unreachable, }, - .vector, .bool, .int, .float => { - return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(Zt)}); + .vector, .bool, .int, .float => if (opt) { + return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(?T)}); + } else { + return self.failNodeFmt(node, "expected type '{s}'", .{@typeName(T)}); }, - .@"enum" => if (is_opt) { + .@"enum" => if (opt) { return self.failNode(node, "expected optional enum literal"); } else { return self.failNode(node, "expected enum literal"); }, - else => @compileError(@typeName(Zt)), + .optional => |optional| { + return self.failExpectedTypeInner(optional.child, true, node); + }, + else => comptime unreachable, } } @@ -581,7 +553,7 @@ const Parser = struct { } } - fn parseInt(self: @This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseInt(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return std.math.cast(T, val) orelse @@ -598,7 +570,7 @@ const Parser = struct { } } - fn parseFloat(self: @This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseFloat(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .int_literal => |int| switch (int) { .small => |val| return @floatFromInt(val), @@ -613,7 +585,7 @@ const Parser = struct { } } - fn parseEnumLiteral(self: @This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseEnumLiteral(self: @This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .enum_literal => |field_name| { // Create a comptime string map for the enum fields @@ -633,7 +605,7 @@ const Parser = struct { } } - fn parsePointer(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parsePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .string_literal => return self.parseString(T, node), .array_literal => |nodes| return self.parseSlice(T, nodes), @@ -642,7 +614,7 @@ const Parser = struct { } } - fn parseString(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseString(self: *@This(), T: type, node: Zoir.Node.Index) !T { const ast_node = node.getAstNode(self.zoir); const pointer = @typeInfo(T).pointer; var size_hint = ZonGen.strLitSizeHint(self.ast, ast_node); @@ -675,7 +647,7 @@ const Parser = struct { } } - fn parseSlice(self: *@This(), comptime T: type, nodes: Zoir.Node.Index.Range) !T { + fn parseSlice(self: *@This(), T: type, nodes: Zoir.Node.Index.Range) !T { const pointer = @typeInfo(T).pointer; // Make sure we're working with a slice @@ -706,7 +678,7 @@ const Parser = struct { return slice; } - fn parseArray(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseArray(self: *@This(), T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -745,7 +717,7 @@ const Parser = struct { return result; } - fn parseStruct(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseStruct(self: *@This(), T: type, node: Zoir.Node.Index) !T { const repr = node.get(self.zoir); const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) { .struct_literal => |nodes| nodes, @@ -835,7 +807,7 @@ const Parser = struct { return result; } - fn parseTuple(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseTuple(self: *@This(), T: type, node: Zoir.Node.Index) !T { const nodes: Zoir.Node.Index.Range = switch (node.get(self.zoir)) { .array_literal => |nodes| nodes, .empty_literal => .{ .start = node, .len = 0 }, @@ -882,7 +854,7 @@ const Parser = struct { return result; } - fn parseUnion(self: *@This(), comptime T: type, node: Zoir.Node.Index) !T { + fn parseUnion(self: *@This(), T: type, node: Zoir.Node.Index) !T { const @"union" = @typeInfo(T).@"union"; const field_infos = @"union".fields; @@ -956,7 +928,7 @@ const Parser = struct { fn parseVector( self: *@This(), - comptime T: type, + T: type, node: Zoir.Node.Index, ) !T { const vector_info = @typeInfo(T).vector; @@ -978,6 +950,7 @@ const Parser = struct { } for (0..vector_info.len) |i| { + errdefer for (0..i) |j| free(self.gpa, result[j]); result[i] = try self.parseExpr(vector_info.child, nodes.at(@intCast(i))); } @@ -1058,7 +1031,7 @@ const Parser = struct { fn failCannotRepresent( self: @This(), - comptime T: type, + T: type, node: Zoir.Node.Index, ) error{ OutOfMemory, ParseZon } { @branchHint(.cold); @@ -1150,7 +1123,7 @@ const Parser = struct { } }; -fn intFromFloatExact(comptime T: type, value: anytype) ?T { +fn intFromFloatExact(T: type, value: anytype) ?T { if (value > std.math.maxInt(T) or value < std.math.minInt(T)) { return null; } @@ -3094,6 +3067,17 @@ test "std.zon vector" { ); try std.testing.expectFmt("1:1: error: expected type '@Vector(3, u8)'\n", "{}", .{status}); } + + // Elements should get freed on error + { + var status: Status = .{}; + defer status.deinit(gpa); + try std.testing.expectError( + error.ParseZon, + fromSlice(@Vector(3, *u8), gpa, ".{1, true, 3}", &status, .{}), + ); + try std.testing.expectFmt("1:6: error: expected type 'u8'\n", "{}", .{status}); + } } test "std.zon add pointers" { From 9830645849987a889feedfe655dfc18eface56dd Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 17:33:38 -0800 Subject: [PATCH 84/98] Simplifies pointer logic in comptime parser --- lib/std/zon/parse.zig | 18 ++-- src/Sema/LowerZon.zig | 196 +++++++++++++++++------------------------- 2 files changed, 89 insertions(+), 125 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 956935c7af0c..f16774888753 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -442,13 +442,15 @@ const Parser = struct { .int => return self.parseInt(T, node), .float => return self.parseFloat(T, node), .@"enum" => return self.parseEnumLiteral(T, node), - .pointer => |pointer| if (pointer.size == .one) { - const result = try self.gpa.create(pointer.child); - errdefer self.gpa.destroy(result); - result.* = try self.parseExprInner(pointer.child, node); - return result; - } else { - return self.parsePointer(T, node); + .pointer => |pointer| switch (pointer.size) { + .one => { + const result = try self.gpa.create(pointer.child); + errdefer self.gpa.destroy(result); + result.* = try self.parseExprInner(pointer.child, node); + return result; + }, + .slice => return self.parseSlicePointer(T, node), + else => comptime unreachable, }, .array => return self.parseArray(T, node), .@"struct" => |@"struct"| if (@"struct".is_tuple) @@ -605,7 +607,7 @@ const Parser = struct { } } - fn parsePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { + fn parseSlicePointer(self: *@This(), T: type, node: Zoir.Node.Index) !T { switch (node.get(self.zoir)) { .string_literal => return self.parseString(T, node), .array_literal => |nodes| return self.parseSlice(T, nodes), diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 8f1d3e0e2242..57845591f951 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -76,110 +76,97 @@ fn fail( return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } -/// Given a Zig type, flattens it into a compatible ZON type. -/// -/// For example, `*u32` becomes `u32`, `*?*bool` becomes `?bool`. Does not recurse inside of -/// containers. -/// -/// This is necessary for parsing self referential types (i.e. any tree structure laid out in a -/// human readable form in a config file), and allows putting large optional data behind a -/// pointer. -fn zonType(self: *LowerZon, ty: Type) CompileError!Type { - const zcu = self.sema.pt.zcu; - var is_opt: bool = false; - var zt = ty; - while (true) { - switch (zt.zigTypeTag(zcu)) { - .optional => { - if (is_opt) { - @branchHint(.cold); - return self.sema.failWithOwnedErrorMsg(self.block, try Zcu.ErrorMsg.create( - zcu.gpa, - self.import_loc, - "result type contains nested optionals; not representable in ZON", - .{}, - )); +fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { + const pt = self.sema.pt; + return self.lowerExprInner(node, res_ty) catch |err| switch (err) { + error.WrongType => { + // Flatten to a type directly compatible with ZON before reporting the error + var display_ty = res_ty; + var opt = false; + + while (true) { + switch (display_ty.zigTypeTag(pt.zcu)) { + .optional => { + opt = true; + display_ty = display_ty.childType(pt.zcu); + }, + .pointer => { + const ptr_info = res_ty.ptrInfo(pt.zcu); + if (ptr_info.flags.size == .one) { + display_ty = .fromInterned(display_ty.ptrInfo(pt.zcu).child); + } else { + break; + } + }, + else => break, } - is_opt = true; - zt = zt.optionalChild(zcu); - }, - .pointer => { - if (!zt.isSinglePointer(zcu)) break; - zt = .fromInterned(zt.ptrInfo(zcu).child); - }, - else => break, - } - } - if (is_opt) zt = .fromInterned(try self.sema.pt.intern(.{ .opt_type = zt.toIntern() })); - return zt; -} + } -/// Converts a value from a flattened Zon type back to a Zig type. See `ZonType`. -fn toZigVal(self: *LowerZon, res_ty: Type, val: Value) CompileError!InternPool.Index { - const pt = self.sema.pt; + if (opt) { + display_ty = .fromInterned(try pt.intern(.{ + .opt_type = display_ty.toIntern(), + })); + } - if (val.typeOf(pt.zcu).eql(res_ty, pt.zcu)) { - return val.toIntern(); - } + // Report the error + return self.fail( + node, + "expected type '{}'", + .{display_ty.fmt(self.sema.pt)}, + ); + }, + else => |e| return e, + }; +} +fn lowerExprInner( + self: *LowerZon, + node: Zoir.Node.Index, + res_ty: Type, +) (CompileError || error{WrongType})!InternPool.Index { + const pt = self.sema.pt; switch (res_ty.zigTypeTag(pt.zcu)) { .optional => return pt.intern(.{ .opt = .{ .ty = res_ty.toIntern(), - .val = if (val.optionalValue(pt.zcu)) |inner| b: { - break :b try self.toZigVal(res_ty.optionalChild(pt.zcu), inner); - } else b: { + .val = if (node.get(self.file.zoir.?) == .null) b: { break :b .none; + } else b: { + const child_type = res_ty.optionalChild(pt.zcu); + break :b try self.lowerExprInner(node, child_type); }, }, }), - .pointer => return pt.intern(.{ - .ptr = .{ - .ty = res_ty.toIntern(), - .base_addr = .{ - .uav = .{ - .orig_ty = res_ty.toIntern(), - .val = try self.toZigVal(res_ty.childType(pt.zcu), val), + .pointer => { + const ptr_info = res_ty.ptrInfo(pt.zcu); + switch (ptr_info.flags.size) { + .one => return pt.intern(.{ .ptr = .{ + .ty = res_ty.toIntern(), + .base_addr = .{ + .uav = .{ + .orig_ty = res_ty.toIntern(), + .val = try self.lowerExprInner(node, .fromInterned(ptr_info.child)), + }, }, + .byte_offset = 0, + } }), + .slice => return self.lowerSlice(node, res_ty), + else => { + // Unsupported pointer type, checked in `lower` + unreachable; }, - .byte_offset = 0, - }, - }), - else => unreachable, - } -} - -fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { - const pt = self.sema.pt; - - // Flatten the result type into a compatible ZON type - const flattened_ty = try self.zonType(res_ty); - - // If we're optional, check for null or get the child type - const child_type: Type = switch (pt.zcu.intern_pool.indexToKey(flattened_ty.toIntern())) { - .opt_type => |child_type| if (node.get(self.file.zoir.?) == .null) { - const none = try pt.intern(.{ .opt = .{ - .ty = flattened_ty.toIntern(), - .val = .none, - } }); - return self.toZigVal(res_ty, .fromInterned(none)); - } else .fromInterned(child_type), - else => flattened_ty, - }; - - // Parse to the child type - const child_val = switch (child_type.zigTypeTag(pt.zcu)) { - .bool => self.lowerBool(node), - .int, .comptime_int => self.lowerInt(node, child_type), - .float, .comptime_float => self.lowerFloat(node, child_type), - .null => self.lowerNull(node), - .@"enum" => self.lowerEnum(node, child_type), - .enum_literal => self.lowerEnumLiteral(node), - .array => self.lowerArray(node, child_type), - .@"struct" => self.lowerStructOrTuple(node, child_type), - .@"union" => self.lowerUnion(node, child_type), - .pointer => self.lowerSlice(node, child_type), - .vector => self.lowerVector(node, child_type), + } + }, + .bool => return self.lowerBool(node), + .int, .comptime_int => return self.lowerInt(node, res_ty), + .float, .comptime_float => return self.lowerFloat(node, res_ty), + .null => return self.lowerNull(node), + .@"enum" => return self.lowerEnum(node, res_ty), + .enum_literal => return self.lowerEnumLiteral(node), + .array => return self.lowerArray(node, res_ty), + .@"struct" => return self.lowerStructOrTuple(node, res_ty), + .@"union" => return self.lowerUnion(node, res_ty), + .vector => return self.lowerVector(node, res_ty), .type, .noreturn, @@ -191,33 +178,8 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! .frame, .@"anyframe", .void, - => return self.fail(node, "type '{}' not available in ZON", .{child_type.fmt(pt)}), - - .optional => { - // We unwrapped optional types above, and `zonType` never returns double optionals since - // they aren't allowed in ZON - unreachable; - }, - } catch |err| switch (err) { - error.WrongType => return self.fail( - node, - "expected type '{}'", - .{flattened_ty.fmt(self.sema.pt)}, - ), - else => |e| return e, - }; - - // Wrap the child type if an optional if necessary - const flattened_val = switch (flattened_ty.zigTypeTag(pt.zcu)) { - .optional => try pt.intern(.{ .opt = .{ - .ty = flattened_ty.toIntern(), - .val = child_val, - } }), - else => child_val, - }; - - // Convert back to the Zig type - return self.toZigVal(res_ty, .fromInterned(flattened_val)); + => return self.fail(node, "type '{}' not available in ZON", .{res_ty.fmt(pt)}), + } } fn lowerBool(self: *LowerZon, node: Zoir.Node.Index) !InternPool.Index { From 39ef116873818c0aaba4e570fb00af1783aca1a7 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 19:27:24 -0800 Subject: [PATCH 85/98] Checks types before parsing or serializing at runtime --- lib/std/zon/parse.zig | 113 +++++++++++++++++++++++++++++++++++ lib/std/zon/stringify.zig | 122 ++++++++++++++++++++++++++++++++++---- 2 files changed, 225 insertions(+), 10 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index f16774888753..cdec8807b407 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -321,6 +321,8 @@ pub fn fromZoirNode( status: ?*Status, options: Options, ) error{ OutOfMemory, ParseZon }!T { + comptimeAssertCanParseType(T); + if (status) |s| { s.assertEmpty(); s.ast = ast; @@ -1137,6 +1139,117 @@ fn intFromFloatExact(T: type, value: anytype) ?T { return @intFromFloat(value); } +fn comptimeAssertCanParseType(T: type) void { + if (!comptime canParseType(T)) { + @compileError("cannot parse type '" ++ @typeName(T) ++ "'"); + } +} + +fn canParseType(T: type) bool { + comptime { + var visited: []type = &.{}; + return canParseTypeInner(T, false, &visited); + } +} + +fn canParseTypeInner(T: type, is_opt: bool, visited: *[]type) bool { + // If we've already visited this type, then it must be supported, return true to avoid infinite + // recursion. + for (visited.*) |V| if (V == T) return true; + comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; + visited.* = &new_visited; + + // Check if this type is supported, recursive if needed + switch (@typeInfo(T)) { + .bool, + .int, + .float, + .noreturn, + .null, + .@"enum", + => return true, + + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + .comptime_float, + .comptime_int, + .enum_literal, + => return false, + + .pointer => |pointer| { + switch (pointer.size) { + .one => return canParseTypeInner(pointer.child, is_opt, visited), + .slice => return canParseTypeInner(pointer.child, false, visited), + .many, .c => return false, + } + }, + .array => |array| return canParseTypeInner(array.child, false, visited), + .@"struct" => |@"struct"| { + inline for (@"struct".fields) |field| { + if (!field.is_comptime and !canParseTypeInner(field.type, false, visited)) { + return false; + } + } + return true; + }, + .optional => |optional| { + // Forbid nested optionals + if (is_opt) return false; + // Other optionals are fine + return canParseTypeInner(optional.child, true, visited); + }, + .@"union" => |@"union"| { + inline for (@"union".fields) |field| { + if (field.type != void and !canParseTypeInner(field.type, false, visited)) { + return false; + } + } + return true; + }, + .vector => |vector| return canParseTypeInner(vector.child, false, visited), + } +} + +test "std.zon parse canParseType" { + try std.testing.expect(!comptime canParseType(void)); + try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); + try std.testing.expect(!comptime canParseType(struct { error{foo} })); + try std.testing.expect(!comptime canParseType(union { a: void, b: [*c]u8 })); + try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8))); + try std.testing.expect(!comptime canParseType(*?[*c]u8)); + try std.testing.expect(comptime canParseType(enum(u8) { _ })); + try std.testing.expect(comptime canParseType(union { foo: void })); + try std.testing.expect(comptime canParseType(union(enum) { foo: void })); + try std.testing.expect(!comptime canParseType(comptime_float)); + try std.testing.expect(!comptime canParseType(comptime_int)); + try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); + try std.testing.expect(comptime canParseType(?u8)); + try std.testing.expect(comptime canParseType(*?*u8)); + try std.testing.expect(comptime canParseType(?struct { + foo: ?struct { + ?union(enum) { + a: ?@Vector(0, ?*u8), + }, + ?struct { + f: ?[]?u8, + }, + }, + })); + try std.testing.expect(!comptime canParseType(??u8)); + try std.testing.expect(!comptime canParseType(?*?u8)); + try std.testing.expect(!comptime canParseType(*?*?*u8)); + const Recursive = struct { foo: ?*@This() }; + try std.testing.expect(comptime canParseType(Recursive)); +} + test "std.zon requiresAllocator" { try std.testing.expect(!requiresAllocator(u8)); try std.testing.expect(!requiresAllocator(f32)); diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 505a94de3d67..13f8ec3cf2f9 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -127,6 +127,115 @@ fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []type) bool { return false; } +fn comptimeAssertCanSerializeType(T: type) void { + if (!comptime canSerializeType(T)) { + @compileError("cannot serialize type '" ++ @typeName(T) ++ "'"); + } +} + +fn canSerializeType(T: type) bool { + var visited: []type = &.{}; + return canSerializeTypeInner(T, false, &visited); +} + +fn canSerializeTypeInner(T: type, is_opt: bool, visited: *[]type) bool { + // If we've already visited this type, then it must be supported, return true to avoid infinite + // recursion. + for (visited.*) |V| if (V == T) return true; + comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; + visited.* = &new_visited; + + // Check if this type is supported, recursive if needed + switch (@typeInfo(T)) { + .bool, + .int, + .float, + .comptime_float, + .comptime_int, + .noreturn, + .null, + .enum_literal, + => return true, + + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + => return false, + + .@"enum" => |@"enum"| return @"enum".is_exhaustive, + + .pointer => |pointer| { + switch (pointer.size) { + .one => return canSerializeTypeInner(pointer.child, is_opt, visited), + .slice => return canSerializeTypeInner(pointer.child, false, visited), + .many, .c => return false, + } + }, + .array => |array| return canSerializeTypeInner(array.child, false, visited), + .@"struct" => |@"struct"| { + inline for (@"struct".fields) |field| { + if (!canSerializeTypeInner(field.type, false, visited)) return false; + } + return true; + }, + .optional => |optional| { + // Forbid nested optionals + if (is_opt) return false; + // Other optionals are fine + return canSerializeTypeInner(optional.child, true, visited); + }, + .@"union" => |@"union"| { + if (@"union".tag_type == null) return false; + inline for (@"union".fields) |field| { + if (field.type != void and !canSerializeTypeInner(field.type, false, visited)) { + return false; + } + } + return true; + }, + .vector => |vector| return canSerializeTypeInner(vector.child, false, visited), + } +} + +test "std.zon stringify canSerializeType" { + try std.testing.expect(!comptime canSerializeType(void)); + try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); + try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); + try std.testing.expect(!comptime canSerializeType(union { a: void, f: [*c]u8 })); + try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); + try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); + try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); + try std.testing.expect(!comptime canSerializeType(union { foo: void })); + try std.testing.expect(comptime canSerializeType(union(enum) { foo: void })); + try std.testing.expect(comptime canSerializeType(comptime_float)); + try std.testing.expect(comptime canSerializeType(comptime_int)); + try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(comptime canSerializeType(@TypeOf(.foo))); + try std.testing.expect(comptime canSerializeType(?u8)); + try std.testing.expect(comptime canSerializeType(*?*u8)); + try std.testing.expect(comptime canSerializeType(?struct { + foo: ?struct { + ?union(enum) { + a: ?@Vector(0, ?*u8), + }, + ?struct { + f: ?[]?u8, + }, + }, + })); + try std.testing.expect(!comptime canSerializeType(??u8)); + try std.testing.expect(!comptime canSerializeType(?*?u8)); + try std.testing.expect(!comptime canSerializeType(*?*?*u8)); + const Recursive = struct { foo: ?*@This() }; + try std.testing.expect(comptime canSerializeType(Recursive)); +} + test "std.zon typeIsRecursive" { try std.testing.expect(!typeIsRecursive(bool)); try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 })); @@ -363,6 +472,7 @@ pub fn Serializer(Writer: type) type { val: anytype, options: ValueOptions, ) Writer.Error!void { + comptimeAssertCanSerializeType(@TypeOf(val)); switch (@typeInfo(@TypeOf(val))) { .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { self.codePoint(c) catch |err| switch (err) { @@ -375,10 +485,7 @@ pub fn Serializer(Writer: type) type { .float, .comptime_float => try self.float(val), .bool, .null => try std.fmt.format(self.writer, "{}", .{val}), .enum_literal => try self.ident(@tagName(val)), - .@"enum" => |@"enum"| b: { - comptime assert(@"enum".is_exhaustive); - break :b try self.ident(@tagName(val)); - }, + .@"enum" => try self.ident(@tagName(val)), .void => try self.writer.writeAll("{}"), .pointer => |pointer| { // Try to serialize as a string @@ -558,6 +665,7 @@ pub fn Serializer(Writer: type) type { } fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { + comptimeAssertCanSerializeType(@TypeOf(val)); switch (@typeInfo(@TypeOf(val))) { .@"struct" => { var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); @@ -1977,7 +2085,6 @@ test "std.zon stringify primitives" { \\ .a = true, \\ .b = false, \\ .c = .foo, - \\ .d = {}, \\ .e = null, \\} , @@ -1985,7 +2092,6 @@ test "std.zon stringify primitives" { .a = true, .b = false, .c = .foo, - .d = {}, .e = null, }, .{}, @@ -2157,10 +2263,6 @@ test "std.zon pointers" { try expectSerializeEqual("10", @as(*const *const ?u32, &&10), .{}); try expectSerializeEqual("null", @as(*const *const ?u32, &&null), .{}); - // Nested optionals are fine when serializing - try expectSerializeEqual("10", @as(??u32, 10), .{}); - try expectSerializeEqual("null", @as(??u32, null), .{}); - try expectSerializeEqual(".{ 1, 2 }", &[2]u32{ 1, 2 }, .{}); // A complicated type with nested internal pointers and string allocations From b89c2b696f69e1baa501ff564f7e4c5da2aecfab Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 20:29:33 -0800 Subject: [PATCH 86/98] Checks types at comptime --- lib/std/zon/parse.zig | 2 +- lib/std/zon/stringify.zig | 2 +- src/Sema/LowerZon.zig | 128 ++++++++++++++++++ test/behavior/zon.zig | 6 + test/behavior/zon/recursive.zon | 1 + .../compile_errors/@import_zon_bad_type.zig | 99 ++++++++++++++ 6 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 test/behavior/zon/recursive.zon create mode 100644 test/cases/compile_errors/@import_zon_bad_type.zig diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index cdec8807b407..c1103ce9d4b0 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1221,7 +1221,7 @@ test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(void)); try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); try std.testing.expect(!comptime canParseType(struct { error{foo} })); - try std.testing.expect(!comptime canParseType(union { a: void, b: [*c]u8 })); + try std.testing.expect(!comptime canParseType(union(enum) { a: void, b: [*c]u8 })); try std.testing.expect(!comptime canParseType(@Vector(0, [*c]u8))); try std.testing.expect(!comptime canParseType(*?[*c]u8)); try std.testing.expect(comptime canParseType(enum(u8) { _ })); diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 13f8ec3cf2f9..6df4aaf39488 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -207,7 +207,7 @@ test "std.zon stringify canSerializeType" { try std.testing.expect(!comptime canSerializeType(void)); try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 })); try std.testing.expect(!comptime canSerializeType(struct { error{foo} })); - try std.testing.expect(!comptime canSerializeType(union { a: void, f: [*c]u8 })); + try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 })); try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8))); try std.testing.expect(!comptime canSerializeType(*?[*c]u8)); try std.testing.expect(!comptime canSerializeType(enum(u8) { _ })); diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 57845591f951..a65bbddafcfd 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -46,9 +46,127 @@ pub fn lower( .block = block, }; + // XXX: add tests covering this, including recursive types! + try lower_zon.checkParseType(res_ty); + return lower_zon.lowerExpr(.root, res_ty); } +fn checkParseType(self: *LowerZon, ty: Type) !void { + var visited: std.AutoHashMapUnmanaged(Type, void) = .empty; + try self.checkParseTypeInner(ty, .none, &visited); +} + +fn checkParseTypeInner( + self: *LowerZon, + ty: Type, + opt_ancestor: InternPool.Index, + visited: *std.AutoHashMapUnmanaged(Type, void), +) !void { + const pt = self.sema.pt; + + // XXX: wait what if it contains it already but the thing it contains is optional?? need a bool + // for whether or not it's optional or something...or to avoid this some other way. or we record + // a bool in the hashmap and reivist. or whatever. + // ah maybe we only check this when at a pointer? no doesn't help. + // XXX: make sure we have tests for it being ALLOWED to nest the right ways + const gop = try visited.getOrPut(pt.zcu.gpa, ty); + if (gop.found_existing) return; + + switch (ty.zigTypeTag(pt.zcu)) { + .bool, + .int, + .float, + .null, + .@"enum", + .comptime_float, + .comptime_int, + .enum_literal, + => {}, + + .noreturn, + .void, + .type, + .undefined, + .error_union, + .error_set, + .@"fn", + .frame, + .@"anyframe", + .@"opaque", + => return self.failUnsupportedResultType( + "result type unsupported by ZON; result type contains '{}'", + .{ty.fmt(pt)}, + ), + + .pointer => { + const ptr_info = ty.ptrInfo(pt.zcu); + switch (ptr_info.flags.size) { + .one => try self.checkParseTypeInner( + .fromInterned(ptr_info.child), + opt_ancestor, + visited, + ), + .slice => try self.checkParseTypeInner( + .fromInterned(ptr_info.child), + .none, + visited, + ), + .many, .c => return self.failUnsupportedResultType( + "result type unsupported by ZON; result type contains '{}'", + .{ty.fmt(pt)}, + ), + } + }, + .array => { + const array_info = ty.arrayInfo(pt.zcu); + try self.checkParseTypeInner(array_info.elem_type, .none, visited); + }, + .@"struct" => { + if (ty.isTuple(pt.zcu)) { + const tuple_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).tuple_type; + const field_types = tuple_info.types.get(&pt.zcu.intern_pool); + const field_comptime_vals = tuple_info.values.get(&pt.zcu.intern_pool); + for (field_types, 0..) |field_type, i| { + if (field_comptime_vals.len == 0 or field_comptime_vals[i] == .none) { + try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + } + } + } else { + try ty.resolveFields(pt); + const struct_info = pt.zcu.typeToStruct(ty).?; + for (struct_info.field_types.get(&pt.zcu.intern_pool), 0..) |field_type, i| { + if (!struct_info.comptime_bits.getBit(&pt.zcu.intern_pool, i)) { + try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + } + } + } + }, + .optional => { + if (opt_ancestor != .none) { + return self.failUnsupportedResultType( + "nested optionals not available in ZON; result type contains '{}'", + .{Type.fromInterned(opt_ancestor).fmt(pt)}, + ); + } + try self.checkParseTypeInner(ty.optionalChild(pt.zcu), ty.toIntern(), visited); + }, + .@"union" => { + try ty.resolveFields(pt); + const union_info = pt.zcu.typeToUnion(ty).?; + for (union_info.field_types.get(&pt.zcu.intern_pool)) |field_type| { + if (field_type != .void_type) { + try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + } + } + }, + .vector => { + const vector_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).vector_type; + try self.checkParseTypeInner(.fromInterned(vector_info.child), .none, visited); + }, + } +} + fn lazySrcLoc(self: *LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { if (self.base_node_inst == .none) { self.base_node_inst = (try self.sema.pt.zcu.intern_pool.trackZir( @@ -63,6 +181,16 @@ fn lazySrcLoc(self: *LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { }; } +fn failUnsupportedResultType( + self: *LowerZon, + comptime format: []const u8, + args: anytype, +) error{ AnalysisFail, OutOfMemory } { + @branchHint(.cold); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.import_loc, format, args); + return self.sema.failWithOwnedErrorMsg(self.block, err_msg); +} + fn fail( self: *LowerZon, node: Zoir.Node.Index, diff --git a/test/behavior/zon.zig b/test/behavior/zon.zig index 3366588e383d..2003a8760004 100644 --- a/test/behavior/zon.zig +++ b/test/behavior/zon.zig @@ -511,3 +511,9 @@ test "pointers" { try std.testing.expectEqualDeep(expected, found.?.*); } } + +test "recursive" { + const Recursive = struct { foo: ?*const @This() }; + const expected: Recursive = .{ .foo = &.{ .foo = null } }; + try expectEqualDeep(expected, @as(Recursive, @import("zon/recursive.zon"))); +} diff --git a/test/behavior/zon/recursive.zon b/test/behavior/zon/recursive.zon new file mode 100644 index 000000000000..063bf84915c0 --- /dev/null +++ b/test/behavior/zon/recursive.zon @@ -0,0 +1 @@ +.{ .foo = .{ .foo = null } } diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig new file mode 100644 index 000000000000..f1ad811e7300 --- /dev/null +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -0,0 +1,99 @@ +export fn testVoid() void { + const f: void = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInStruct() void { + const f: struct { f: [*]u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testError() void { + const f: struct { error{foo} } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInUnion() void { + const f: union(enum) { a: void, b: [*c]u8 } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInVector() void { + const f: @Vector(0, [*c]u8) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testInOpt() void { + const f: *?[*c]u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testComptimeField() void { + const f: struct { comptime foo: ??u8 = null } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testEnumLiteral() void { + const f: @TypeOf(.foo) = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt1() void { + const f: ??u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt2() void { + const f: ?*?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNestedOpt3() void { + const f: *?*?*u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testOpt() void { + const f: ?u8 = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testNonExhaustiveEnum() void { + const f: enum(u8) { _ } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testUntaggedUnion() void { + const f: union { foo: void } = @import("zon/neg_inf.zon"); + _ = f; +} + +export fn testTaggedUnionVoid() void { + const f: union(enum) { foo: void } = @import("zon/neg_inf.zon"); + _ = f; +} + +// error +// imports=zon/neg_inf.zon +// +// tmp.zig:2:29: error: result type unsupported by ZON; result type contains 'void' +// tmp.zig:7:44: error: result type unsupported by ZON; result type contains '[*]u8' +// tmp.zig:12:46: error: result type unsupported by ZON; result type contains 'error{foo}' +// tmp.zig:17:59: error: result type unsupported by ZON; result type contains '[*c]u8' +// tmp.zig:22:43: error: result type unsupported by ZON; result type contains '[*c]u8' +// tmp.zig:27:33: error: result type unsupported by ZON; result type contains '[*c]u8' +// tmp.zig:42:29: error: nested optionals not available in ZON; result type contains '??u8' +// tmp.zig:47:30: error: nested optionals not available in ZON; result type contains '?*?u8' +// tmp.zig:52:32: error: nested optionals not available in ZON; result type contains '?*?*u8' +// neg_inf.zon:1:1: error: expected type 'tmp.testComptimeField__struct_471' +// tmp.zig:32:61: note: imported here +// neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' +// tmp.zig:37:38: note: imported here +// neg_inf.zon:1:1: error: expected type '?u8' +// tmp.zig:57:28: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_489' +// tmp.zig:62:39: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_491' +// tmp.zig:67:44: note: imported here +// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_494' +// tmp.zig:72:50: note: imported here From 05d7d5c885e168927a8d2553b767710aed77b441 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 21:55:59 -0800 Subject: [PATCH 87/98] Fixes interaction between visited check & nested optionals for ZON type compatability --- lib/std/zon/parse.zig | 61 +++++++++++++----- lib/std/zon/stringify.zig | 59 ++++++++++++----- src/Sema/LowerZon.zig | 64 ++++++++++--------- .../compile_errors/@import_zon_bad_type.zig | 11 ++++ 4 files changed, 131 insertions(+), 64 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index c1103ce9d4b0..531c5000a68b 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1148,18 +1148,20 @@ fn comptimeAssertCanParseType(T: type) void { fn canParseType(T: type) bool { comptime { var visited: []type = &.{}; - return canParseTypeInner(T, false, &visited); + return canParseTypeInner(T, &visited); } } -fn canParseTypeInner(T: type, is_opt: bool, visited: *[]type) bool { - // If we've already visited this type, then it must be supported, return true to avoid infinite - // recursion. +fn canParseTypeInner(T: type, visited: *[]type) bool { + // Check if we're a nested optional before we possibly return early due to recursion + if (isNestedOptional(T)) return false; + + // Check/update the visited list to avoid infinite recursion for (visited.*) |V| if (V == T) return true; comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; visited.* = &new_visited; - // Check if this type is supported, recursive if needed + // Check if this type is supported, recurse if needed switch (@typeInfo(T)) { .bool, .int, @@ -1185,35 +1187,53 @@ fn canParseTypeInner(T: type, is_opt: bool, visited: *[]type) bool { .pointer => |pointer| { switch (pointer.size) { - .one => return canParseTypeInner(pointer.child, is_opt, visited), - .slice => return canParseTypeInner(pointer.child, false, visited), + .one => return canParseTypeInner(pointer.child, visited), + .slice => return canParseTypeInner(pointer.child, visited), .many, .c => return false, } }, - .array => |array| return canParseTypeInner(array.child, false, visited), + .array => |array| return canParseTypeInner(array.child, visited), .@"struct" => |@"struct"| { inline for (@"struct".fields) |field| { - if (!field.is_comptime and !canParseTypeInner(field.type, false, visited)) { + if (!field.is_comptime and !canParseTypeInner(field.type, visited)) { return false; } } return true; }, - .optional => |optional| { - // Forbid nested optionals - if (is_opt) return false; - // Other optionals are fine - return canParseTypeInner(optional.child, true, visited); - }, + .optional => |optional| return canParseTypeInner(optional.child, visited), .@"union" => |@"union"| { inline for (@"union".fields) |field| { - if (field.type != void and !canParseTypeInner(field.type, false, visited)) { + if (field.type != void and !canParseTypeInner(field.type, visited)) { return false; } } return true; }, - .vector => |vector| return canParseTypeInner(vector.child, false, visited), + .vector => |vector| return canParseTypeInner(vector.child, visited), + } +} + +/// Returns true if this type is optional, and it contains another optional either directly or +/// through a series of single item pointers. +fn isNestedOptional(T: type) bool { + comptime switch (@typeInfo(T)) { + .optional => |optional| return isNestedOptionalInner(optional.child), + else => return false, + }; +} + +fn isNestedOptionalInner(T: type) bool { + switch (@typeInfo(T)) { + .pointer => |pointer| { + if (pointer.size == .one) { + return isNestedOptionalInner(pointer.child); + } else { + return false; + } + }, + .optional => return true, + else => return false, } } @@ -1248,6 +1268,13 @@ test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(*?*?*u8)); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canParseType(Recursive)); + + // Make sure we validate nested optional before we early out due to already having seen + // a type recursion! + try std.testing.expect(!comptime canParseType(struct { + add_to_visited: ?u8, + retrieve_from_visited: ??u8, + })); } test "std.zon requiresAllocator" { diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 6df4aaf39488..bdcdaba3cbce 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -135,17 +135,19 @@ fn comptimeAssertCanSerializeType(T: type) void { fn canSerializeType(T: type) bool { var visited: []type = &.{}; - return canSerializeTypeInner(T, false, &visited); + return canSerializeTypeInner(T, &visited); } -fn canSerializeTypeInner(T: type, is_opt: bool, visited: *[]type) bool { - // If we've already visited this type, then it must be supported, return true to avoid infinite - // recursion. +fn canSerializeTypeInner(T: type, visited: *[]type) bool { + // Check if we're a nested optional before we possibly return early due to recursion + if (isNestedOptional(T)) return false; + + // Check/update the visited list to avoid infinite recursion for (visited.*) |V| if (V == T) return true; comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; visited.* = &new_visited; - // Check if this type is supported, recursive if needed + // Check if this type is supported, recurse if needed switch (@typeInfo(T)) { .bool, .int, @@ -172,34 +174,50 @@ fn canSerializeTypeInner(T: type, is_opt: bool, visited: *[]type) bool { .pointer => |pointer| { switch (pointer.size) { - .one => return canSerializeTypeInner(pointer.child, is_opt, visited), - .slice => return canSerializeTypeInner(pointer.child, false, visited), + .one => return canSerializeTypeInner(pointer.child, visited), + .slice => return canSerializeTypeInner(pointer.child, visited), .many, .c => return false, } }, - .array => |array| return canSerializeTypeInner(array.child, false, visited), + .array => |array| return canSerializeTypeInner(array.child, visited), .@"struct" => |@"struct"| { inline for (@"struct".fields) |field| { - if (!canSerializeTypeInner(field.type, false, visited)) return false; + if (!canSerializeTypeInner(field.type, visited)) return false; } return true; }, - .optional => |optional| { - // Forbid nested optionals - if (is_opt) return false; - // Other optionals are fine - return canSerializeTypeInner(optional.child, true, visited); - }, + .optional => |optional| return canSerializeTypeInner(optional.child, visited), .@"union" => |@"union"| { if (@"union".tag_type == null) return false; inline for (@"union".fields) |field| { - if (field.type != void and !canSerializeTypeInner(field.type, false, visited)) { + if (field.type != void and !canSerializeTypeInner(field.type, visited)) { return false; } } return true; }, - .vector => |vector| return canSerializeTypeInner(vector.child, false, visited), + .vector => |vector| return canSerializeTypeInner(vector.child, visited), + } +} + +fn isNestedOptional(T: type) bool { + comptime switch (@typeInfo(T)) { + .optional => |optional| return isNestedOptionalInner(optional.child), + else => return false, + }; +} + +fn isNestedOptionalInner(T: type) bool { + switch (@typeInfo(T)) { + .pointer => |pointer| { + if (pointer.size == .one) { + return isNestedOptionalInner(pointer.child); + } else { + return false; + } + }, + .optional => return true, + else => return false, } } @@ -234,6 +252,13 @@ test "std.zon stringify canSerializeType" { try std.testing.expect(!comptime canSerializeType(*?*?*u8)); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canSerializeType(Recursive)); + + // Make sure we validate nested optional before we early out due to already having seen + // a type recursion! + try std.testing.expect(!comptime canSerializeType(struct { + add_to_visited: ?u8, + retrieve_from_visited: ??u8, + })); } test "std.zon typeIsRecursive" { diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index a65bbddafcfd..ef3984b59b18 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -46,33 +46,47 @@ pub fn lower( .block = block, }; - // XXX: add tests covering this, including recursive types! - try lower_zon.checkParseType(res_ty); + try lower_zon.checkType(res_ty); return lower_zon.lowerExpr(.root, res_ty); } -fn checkParseType(self: *LowerZon, ty: Type) !void { +fn checkType(self: *LowerZon, ty: Type) !void { var visited: std.AutoHashMapUnmanaged(Type, void) = .empty; - try self.checkParseTypeInner(ty, .none, &visited); + try self.checkTypeInner(ty, &visited); } -fn checkParseTypeInner( +fn checkTypeInner( self: *LowerZon, ty: Type, - opt_ancestor: InternPool.Index, visited: *std.AutoHashMapUnmanaged(Type, void), ) !void { const pt = self.sema.pt; - // XXX: wait what if it contains it already but the thing it contains is optional?? need a bool - // for whether or not it's optional or something...or to avoid this some other way. or we record - // a bool in the hashmap and reivist. or whatever. - // ah maybe we only check this when at a pointer? no doesn't help. - // XXX: make sure we have tests for it being ALLOWED to nest the right ways + // Check if we're a nested optional before we possibly return early due to recursion + if (ty.zigTypeTag(pt.zcu) == .optional) { + var curr = ty.optionalChild(pt.zcu); + while (true) { + switch (curr.zigTypeTag(pt.zcu)) { + .optional => return self.failUnsupportedResultType( + "nested optionals not available in ZON; result type contains '{}'", + .{ty.fmt(pt)}, + ), + .pointer => { + const ptr_info = curr.ptrInfo(pt.zcu); + if (ptr_info.flags.size != .one) break; + curr = .fromInterned(ptr_info.child); + }, + else => break, + } + } + } + + // Check/update the visited list to avoid infinite recursion const gop = try visited.getOrPut(pt.zcu.gpa, ty); if (gop.found_existing) return; + // Check if this type is supported, recurse if needed switch (ty.zigTypeTag(pt.zcu)) { .bool, .int, @@ -102,14 +116,12 @@ fn checkParseTypeInner( .pointer => { const ptr_info = ty.ptrInfo(pt.zcu); switch (ptr_info.flags.size) { - .one => try self.checkParseTypeInner( + .one => try self.checkTypeInner( .fromInterned(ptr_info.child), - opt_ancestor, visited, ), - .slice => try self.checkParseTypeInner( + .slice => try self.checkTypeInner( .fromInterned(ptr_info.child), - .none, visited, ), .many, .c => return self.failUnsupportedResultType( @@ -120,7 +132,7 @@ fn checkParseTypeInner( }, .array => { const array_info = ty.arrayInfo(pt.zcu); - try self.checkParseTypeInner(array_info.elem_type, .none, visited); + try self.checkTypeInner(array_info.elem_type, visited); }, .@"struct" => { if (ty.isTuple(pt.zcu)) { @@ -129,7 +141,7 @@ fn checkParseTypeInner( const field_comptime_vals = tuple_info.values.get(&pt.zcu.intern_pool); for (field_types, 0..) |field_type, i| { if (field_comptime_vals.len == 0 or field_comptime_vals[i] == .none) { - try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + try self.checkTypeInner(.fromInterned(field_type), visited); } } } else { @@ -137,32 +149,24 @@ fn checkParseTypeInner( const struct_info = pt.zcu.typeToStruct(ty).?; for (struct_info.field_types.get(&pt.zcu.intern_pool), 0..) |field_type, i| { if (!struct_info.comptime_bits.getBit(&pt.zcu.intern_pool, i)) { - try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + try self.checkTypeInner(.fromInterned(field_type), visited); } } } }, - .optional => { - if (opt_ancestor != .none) { - return self.failUnsupportedResultType( - "nested optionals not available in ZON; result type contains '{}'", - .{Type.fromInterned(opt_ancestor).fmt(pt)}, - ); - } - try self.checkParseTypeInner(ty.optionalChild(pt.zcu), ty.toIntern(), visited); - }, + .optional => try self.checkTypeInner(ty.optionalChild(pt.zcu), visited), .@"union" => { try ty.resolveFields(pt); const union_info = pt.zcu.typeToUnion(ty).?; for (union_info.field_types.get(&pt.zcu.intern_pool)) |field_type| { if (field_type != .void_type) { - try self.checkParseTypeInner(.fromInterned(field_type), .none, visited); + try self.checkTypeInner(.fromInterned(field_type), visited); } } }, .vector => { const vector_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).vector_type; - try self.checkParseTypeInner(.fromInterned(vector_info.child), .none, visited); + try self.checkTypeInner(.fromInterned(vector_info.child), visited); }, } } @@ -221,7 +225,7 @@ fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError! .pointer => { const ptr_info = res_ty.ptrInfo(pt.zcu); if (ptr_info.flags.size == .one) { - display_ty = .fromInterned(display_ty.ptrInfo(pt.zcu).child); + display_ty = .fromInterned(ptr_info.child); } else { break; } diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index f1ad811e7300..e25480ba2749 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -73,6 +73,16 @@ export fn testTaggedUnionVoid() void { _ = f; } +export fn testVisited() void { + const V = struct { + ?f32, // Adds `?f32` to the visited list + ??f32, // `?f32` is already visited, we need to detect the nested opt anyway + f32, + }; + const f: V = @import("zon/neg_inf.zon"); + _ = f; +} + // error // imports=zon/neg_inf.zon // @@ -85,6 +95,7 @@ export fn testTaggedUnionVoid() void { // tmp.zig:42:29: error: nested optionals not available in ZON; result type contains '??u8' // tmp.zig:47:30: error: nested optionals not available in ZON; result type contains '?*?u8' // tmp.zig:52:32: error: nested optionals not available in ZON; result type contains '?*?*u8' +// tmp.zig:82:26: error: nested optionals not available in ZON; result type contains '??f32' // neg_inf.zon:1:1: error: expected type 'tmp.testComptimeField__struct_471' // tmp.zig:32:61: note: imported here // neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' From c23fa49e3eef4dfdef106278b6b0776a6ab0634d Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 23:23:21 -0800 Subject: [PATCH 88/98] Checks for mutable pointers on import zon --- src/Sema/LowerZon.zig | 6 +++ .../compile_errors/@import_zon_bad_type.zig | 30 ++++++----- .../compile_errors/@import_zon_opt_in_err.zig | 54 +++++++++---------- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index ef3984b59b18..a0ce312b5572 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -115,6 +115,12 @@ fn checkTypeInner( .pointer => { const ptr_info = ty.ptrInfo(pt.zcu); + if (!ptr_info.flags.is_const) { + return self.failUnsupportedResultType( + "result type unsupported by import ZON; result type contains '{}'", + .{ty.fmt(pt)}, + ); + } switch (ptr_info.flags.size) { .one => try self.checkTypeInner( .fromInterned(ptr_info.child), diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index e25480ba2749..98e4d77e5796 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -4,7 +4,7 @@ export fn testVoid() void { } export fn testInStruct() void { - const f: struct { f: [*]u8 } = @import("zon/neg_inf.zon"); + const f: struct { f: [*]const u8 } = @import("zon/neg_inf.zon"); _ = f; } @@ -14,17 +14,17 @@ export fn testError() void { } export fn testInUnion() void { - const f: union(enum) { a: void, b: [*c]u8 } = @import("zon/neg_inf.zon"); + const f: union(enum) { a: void, b: [*c]const u8 } = @import("zon/neg_inf.zon"); _ = f; } export fn testInVector() void { - const f: @Vector(0, [*c]u8) = @import("zon/neg_inf.zon"); + const f: @Vector(0, [*c]const u8) = @import("zon/neg_inf.zon"); _ = f; } export fn testInOpt() void { - const f: *?[*c]u8 = @import("zon/neg_inf.zon"); + const f: *const ?[*c]const u8 = @import("zon/neg_inf.zon"); _ = f; } @@ -44,12 +44,12 @@ export fn testNestedOpt1() void { } export fn testNestedOpt2() void { - const f: ?*?u8 = @import("zon/neg_inf.zon"); + const f: ?*const ?u8 = @import("zon/neg_inf.zon"); _ = f; } export fn testNestedOpt3() void { - const f: *?*?*u8 = @import("zon/neg_inf.zon"); + const f: *const ?*const ?*const u8 = @import("zon/neg_inf.zon"); _ = f; } @@ -83,19 +83,25 @@ export fn testVisited() void { _ = f; } +export fn testMutablePointer() void { + const f: *i32 = @import("zon/neg_inf.zon"); + _ = f; +} + // error // imports=zon/neg_inf.zon // // tmp.zig:2:29: error: result type unsupported by ZON; result type contains 'void' -// tmp.zig:7:44: error: result type unsupported by ZON; result type contains '[*]u8' +// tmp.zig:7:50: error: result type unsupported by ZON; result type contains '[*]const u8' // tmp.zig:12:46: error: result type unsupported by ZON; result type contains 'error{foo}' -// tmp.zig:17:59: error: result type unsupported by ZON; result type contains '[*c]u8' -// tmp.zig:22:43: error: result type unsupported by ZON; result type contains '[*c]u8' -// tmp.zig:27:33: error: result type unsupported by ZON; result type contains '[*c]u8' +// tmp.zig:17:65: error: result type unsupported by ZON; result type contains '[*c]const u8' +// tmp.zig:22:49: error: result type unsupported by ZON; result type contains '[*c]const u8' +// tmp.zig:27:45: error: result type unsupported by ZON; result type contains '[*c]const u8' // tmp.zig:42:29: error: nested optionals not available in ZON; result type contains '??u8' -// tmp.zig:47:30: error: nested optionals not available in ZON; result type contains '?*?u8' -// tmp.zig:52:32: error: nested optionals not available in ZON; result type contains '?*?*u8' +// tmp.zig:47:36: error: nested optionals not available in ZON; result type contains '?*const ?u8' +// tmp.zig:52:50: error: nested optionals not available in ZON; result type contains '?*const ?*const u8' // tmp.zig:82:26: error: nested optionals not available in ZON; result type contains '??f32' +// tmp.zig:87:29: error: result type unsupported by import ZON; result type contains '*i32' // neg_inf.zon:1:1: error: expected type 'tmp.testComptimeField__struct_471' // tmp.zig:32:61: note: imported here // neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' diff --git a/test/cases/compile_errors/@import_zon_opt_in_err.zig b/test/cases/compile_errors/@import_zon_opt_in_err.zig index d38bb458063c..3f068345fc40 100644 --- a/test/cases/compile_errors/@import_zon_opt_in_err.zig +++ b/test/cases/compile_errors/@import_zon_opt_in_err.zig @@ -4,17 +4,17 @@ export fn testFloatA() void { } export fn testFloatB() void { - const f: *?f32 = @import("zon/vec2.zon"); + const f: *const ?f32 = @import("zon/vec2.zon"); _ = f; } export fn testFloatC() void { - const f: ?*f32 = @import("zon/vec2.zon"); + const f: ?*const f32 = @import("zon/vec2.zon"); _ = f; } export fn testFloatD() void { - const f: ?*f32 = @import("zon/vec2.zon"); + const f: ?*const f32 = @import("zon/vec2.zon"); _ = f; } @@ -63,27 +63,27 @@ export fn testVector() void { // error // imports=zon/vec2.zon // -//vec2.zon:1:2: error: expected type '?f32' -//tmp.zig:2:29: note: imported here -//vec2.zon:1:2: error: expected type '?f32' -//tmp.zig:7:30: note: imported here -//vec2.zon:1:2: error: expected type '?f32' -//tmp.zig:12:30: note: imported here -//vec2.zon:1:2: error: expected type '?f32' -//tmp.zig:17:30: note: imported here -//vec2.zon:1:2: error: expected type '?bool' -//tmp.zig:22:30: note: imported here -//vec2.zon:1:2: error: expected type '?i32' -//tmp.zig:27:29: note: imported here -//vec2.zon:1:2: error: expected type '?tmp.Enum' -//tmp.zig:33:30: note: imported here -//vec2.zon:1:2: error: expected type '?@Type(.enum_literal)' -//tmp.zig:38:39: note: imported here -//vec2.zon:1:2: error: expected type '?[1]u8' -//tmp.zig:43:31: note: imported here -//vec2.zon:1:2: error: expected type '?tmp.Union' -//tmp.zig:49:31: note: imported here -//vec2.zon:1:2: error: expected type '?[]const u8' -//tmp.zig:54:36: note: imported here -//vec2.zon:1:2: error: expected type '?@Vector(3, f32)' -//tmp.zig:59:41: note: imported here +// vec2.zon:1:2: error: expected type '?f32' +// tmp.zig:2:29: note: imported here +// vec2.zon:1:2: error: expected type '?f32' +// tmp.zig:7:36: note: imported here +// vec2.zon:1:2: error: expected type '?f32' +// tmp.zig:12:36: note: imported here +// vec2.zon:1:2: error: expected type '?f32' +// tmp.zig:17:36: note: imported here +// vec2.zon:1:2: error: expected type '?bool' +// tmp.zig:22:30: note: imported here +// vec2.zon:1:2: error: expected type '?i32' +// tmp.zig:27:29: note: imported here +// vec2.zon:1:2: error: expected type '?tmp.Enum' +// tmp.zig:33:30: note: imported here +// vec2.zon:1:2: error: expected type '?@Type(.enum_literal)' +// tmp.zig:38:39: note: imported here +// vec2.zon:1:2: error: expected type '?[1]u8' +// tmp.zig:43:31: note: imported here +// vec2.zon:1:2: error: expected type '?tmp.Union' +// tmp.zig:49:31: note: imported here +// vec2.zon:1:2: error: expected type '?[]const u8' +// tmp.zig:54:36: note: imported here +// vec2.zon:1:2: error: expected type '?@Vector(3, f32)' +// tmp.zig:59:41: note: imported here From 2878fb68ca01e7cff5e911229d3b23d8824ad0d6 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 23:37:32 -0800 Subject: [PATCH 89/98] Improves errors --- src/Sema/LowerZon.zig | 31 ++++++++++------- .../compile_errors/@import_zon_bad_type.zig | 33 ++++++++++++------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index a0ce312b5572..5b09016d3bc4 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -69,8 +69,8 @@ fn checkTypeInner( while (true) { switch (curr.zigTypeTag(pt.zcu)) { .optional => return self.failUnsupportedResultType( - "nested optionals not available in ZON; result type contains '{}'", - .{ty.fmt(pt)}, + "nested optionals not available in ZON", + ty, ), .pointer => { const ptr_info = curr.ptrInfo(pt.zcu); @@ -109,16 +109,16 @@ fn checkTypeInner( .@"anyframe", .@"opaque", => return self.failUnsupportedResultType( - "result type unsupported by ZON; result type contains '{}'", - .{ty.fmt(pt)}, + "result type not available in ZON", + ty, ), .pointer => { const ptr_info = ty.ptrInfo(pt.zcu); if (!ptr_info.flags.is_const) { return self.failUnsupportedResultType( - "result type unsupported by import ZON; result type contains '{}'", - .{ty.fmt(pt)}, + "mutable pointers not available in import ZON", + ty, ); } switch (ptr_info.flags.size) { @@ -130,10 +130,11 @@ fn checkTypeInner( .fromInterned(ptr_info.child), visited, ), - .many, .c => return self.failUnsupportedResultType( - "result type unsupported by ZON; result type contains '{}'", - .{ty.fmt(pt)}, + .many => return self.failUnsupportedResultType( + "many item pointers not available in ZON", + ty, ), + .c => return self.failUnsupportedResultType("c pointers not available in ZON", ty), } }, .array => { @@ -193,11 +194,17 @@ fn lazySrcLoc(self: *LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { fn failUnsupportedResultType( self: *LowerZon, - comptime format: []const u8, - args: anytype, + msg: []const u8, + ty: Type, ) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); - const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.import_loc, format, args); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.import_loc, "{s}", .{msg}); + try self.sema.pt.zcu.errNote( + self.import_loc, + err_msg, + "result type contains type '{}'", + .{ty.fmt(self.sema.pt)}, + ); return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index 98e4d77e5796..bdb00c786c8c 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -91,17 +91,28 @@ export fn testMutablePointer() void { // error // imports=zon/neg_inf.zon // -// tmp.zig:2:29: error: result type unsupported by ZON; result type contains 'void' -// tmp.zig:7:50: error: result type unsupported by ZON; result type contains '[*]const u8' -// tmp.zig:12:46: error: result type unsupported by ZON; result type contains 'error{foo}' -// tmp.zig:17:65: error: result type unsupported by ZON; result type contains '[*c]const u8' -// tmp.zig:22:49: error: result type unsupported by ZON; result type contains '[*c]const u8' -// tmp.zig:27:45: error: result type unsupported by ZON; result type contains '[*c]const u8' -// tmp.zig:42:29: error: nested optionals not available in ZON; result type contains '??u8' -// tmp.zig:47:36: error: nested optionals not available in ZON; result type contains '?*const ?u8' -// tmp.zig:52:50: error: nested optionals not available in ZON; result type contains '?*const ?*const u8' -// tmp.zig:82:26: error: nested optionals not available in ZON; result type contains '??f32' -// tmp.zig:87:29: error: result type unsupported by import ZON; result type contains '*i32' +// tmp.zig:2:29: error: result type not available in ZON +// tmp.zig:2:29: note: result type contains type 'void' +// tmp.zig:7:50: error: many item pointers not available in ZON +// tmp.zig:7:50: note: result type contains type '[*]const u8' +// tmp.zig:12:46: error: result type not available in ZON +// tmp.zig:12:46: note: result type contains type 'error{foo}' +// tmp.zig:17:65: error: c pointers not available in ZON +// tmp.zig:17:65: note: result type contains type '[*c]const u8' +// tmp.zig:22:49: error: c pointers not available in ZON +// tmp.zig:22:49: note: result type contains type '[*c]const u8' +// tmp.zig:27:45: error: c pointers not available in ZON +// tmp.zig:27:45: note: result type contains type '[*c]const u8' +// tmp.zig:42:29: error: nested optionals not available in ZON +// tmp.zig:42:29: note: result type contains type '??u8' +// tmp.zig:47:36: error: nested optionals not available in ZON +// tmp.zig:47:36: note: result type contains type '?*const ?u8' +// tmp.zig:52:50: error: nested optionals not available in ZON +// tmp.zig:52:50: note: result type contains type '?*const ?*const u8' +// tmp.zig:82:26: error: nested optionals not available in ZON +// tmp.zig:82:26: note: result type contains type '??f32' +// tmp.zig:87:29: error: mutable pointers not available in import ZON +// tmp.zig:87:29: note: result type contains type '*i32' // neg_inf.zon:1:1: error: expected type 'tmp.testComptimeField__struct_471' // tmp.zig:32:61: note: imported here // neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' From ead4f4cd31961b3a3a465fce2383fc0fae90e67f Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sat, 1 Feb 2025 23:55:15 -0800 Subject: [PATCH 90/98] Cleans up rules on comptime fields --- lib/std/zon/parse.zig | 30 ++++++++++++++++++------------ lib/std/zon/stringify.zig | 15 ++++++++++++--- src/Sema/LowerZon.zig | 13 ++++--------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 531c5000a68b..c250964d87bb 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -1148,11 +1148,11 @@ fn comptimeAssertCanParseType(T: type) void { fn canParseType(T: type) bool { comptime { var visited: []type = &.{}; - return canParseTypeInner(T, &visited); + return canParseTypeInner(T, &visited, false); } } -fn canParseTypeInner(T: type, visited: *[]type) bool { +fn canParseTypeInner(T: type, visited: *[]type, is_comptime: bool) bool { // Check if we're a nested optional before we possibly return early due to recursion if (isNestedOptional(T)) return false; @@ -1180,37 +1180,39 @@ fn canParseTypeInner(T: type, visited: *[]type) bool { .frame, .@"anyframe", .@"opaque", - .comptime_float, + => return false, + .comptime_int, + .comptime_float, .enum_literal, - => return false, + => return is_comptime, .pointer => |pointer| { switch (pointer.size) { - .one => return canParseTypeInner(pointer.child, visited), - .slice => return canParseTypeInner(pointer.child, visited), + .one => return canParseTypeInner(pointer.child, visited, is_comptime), + .slice => return canParseTypeInner(pointer.child, visited, is_comptime), .many, .c => return false, } }, - .array => |array| return canParseTypeInner(array.child, visited), + .array => |array| return canParseTypeInner(array.child, visited, is_comptime), .@"struct" => |@"struct"| { inline for (@"struct".fields) |field| { - if (!field.is_comptime and !canParseTypeInner(field.type, visited)) { + if (!canParseTypeInner(field.type, visited, is_comptime or field.is_comptime)) { return false; } } return true; }, - .optional => |optional| return canParseTypeInner(optional.child, visited), + .optional => |optional| return canParseTypeInner(optional.child, visited, is_comptime), .@"union" => |@"union"| { inline for (@"union".fields) |field| { - if (field.type != void and !canParseTypeInner(field.type, visited)) { + if (field.type != void and !canParseTypeInner(field.type, visited, is_comptime)) { return false; } } return true; }, - .vector => |vector| return canParseTypeInner(vector.child, visited), + .vector => |vector| return canParseTypeInner(vector.child, visited, is_comptime), } } @@ -1249,7 +1251,7 @@ test "std.zon parse canParseType" { try std.testing.expect(comptime canParseType(union(enum) { foo: void })); try std.testing.expect(!comptime canParseType(comptime_float)); try std.testing.expect(!comptime canParseType(comptime_int)); - try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(!comptime canParseType(struct { comptime foo: ??u8 = null })); try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); try std.testing.expect(comptime canParseType(?u8)); try std.testing.expect(comptime canParseType(*?*u8)); @@ -1266,6 +1268,10 @@ test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(??u8)); try std.testing.expect(!comptime canParseType(?*?u8)); try std.testing.expect(!comptime canParseType(*?*?*u8)); + try std.testing.expect(!comptime canParseType(struct { x: comptime_int = 2 })); + try std.testing.expect(!comptime canParseType(struct { x: comptime_float = 2 })); + try std.testing.expect(comptime canParseType(struct { comptime x: @TypeOf(.foo) = .foo })); + try std.testing.expect(!comptime canParseType(struct { comptime_int })); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canParseType(Recursive)); diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index bdcdaba3cbce..d1bfbc431789 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -57,7 +57,8 @@ pub fn serialize( /// Like `serialize`, but recursive types are allowed. /// -/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. +/// Returns `error.ExceededMaxDepth` if `depth` is exceeded. Every nested value adds one to a +/// value's depth. pub fn serializeMaxDepth( val: anytype, options: SerializeOptions, @@ -250,6 +251,10 @@ test "std.zon stringify canSerializeType" { try std.testing.expect(!comptime canSerializeType(??u8)); try std.testing.expect(!comptime canSerializeType(?*?u8)); try std.testing.expect(!comptime canSerializeType(*?*?*u8)); + try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 })); + try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 })); + try std.testing.expect(comptime canSerializeType(struct { comptime_int })); + try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo })); const Recursive = struct { foo: ?*@This() }; try std.testing.expect(comptime canSerializeType(Recursive)); @@ -2252,7 +2257,11 @@ test "std.zon stringify vector" { \\ 6, \\ }, \\ .{ 1, 2 }, - \\ .{ 3, 4 }, + \\ .{ + \\ 3, + \\ 4, + \\ null, + \\ }, \\} , .{ @@ -2263,7 +2272,7 @@ test "std.zon stringify vector" { @Vector(0, u8){}, @Vector(3, u8){ 2, 4, 6 }, @Vector(2, *const u8){ &1, &2 }, - @Vector(2, ?*const u8){ &3, &4 }, + @Vector(3, ?*const u8){ &3, &4, null }, }, .{}, ); diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index 5b09016d3bc4..f00f3745c704 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -145,19 +145,14 @@ fn checkTypeInner( if (ty.isTuple(pt.zcu)) { const tuple_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).tuple_type; const field_types = tuple_info.types.get(&pt.zcu.intern_pool); - const field_comptime_vals = tuple_info.values.get(&pt.zcu.intern_pool); - for (field_types, 0..) |field_type, i| { - if (field_comptime_vals.len == 0 or field_comptime_vals[i] == .none) { - try self.checkTypeInner(.fromInterned(field_type), visited); - } + for (field_types) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), visited); } } else { try ty.resolveFields(pt); const struct_info = pt.zcu.typeToStruct(ty).?; - for (struct_info.field_types.get(&pt.zcu.intern_pool), 0..) |field_type, i| { - if (!struct_info.comptime_bits.getBit(&pt.zcu.intern_pool, i)) { - try self.checkTypeInner(.fromInterned(field_type), visited); - } + for (struct_info.field_types.get(&pt.zcu.intern_pool)) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), visited); } } }, From deda6c1501988048753df72bfc972bc14e8d22d5 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Sun, 2 Feb 2025 00:11:25 -0800 Subject: [PATCH 91/98] Cleans up docs, fixes test --- lib/std/zon/parse.zig | 24 +++++++++-------- lib/std/zon/stringify.zig | 26 +++++++++---------- .../compile_errors/@import_zon_bad_type.zig | 4 +-- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index c250964d87bb..ecb6f6d6188e 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -268,16 +268,18 @@ pub const Status = struct { /// When the parser returns `error.ParseZon`, it will also store a human readable explanation in /// `status` if non null. If status is not null, it must be initialized to `.{}`. pub fn fromSlice( - /// The type to deserialize into. May only transitively contain the following supported types: - /// * bools - /// * fixed sized numeric types - /// * enums - /// * slices - /// * arrays - /// * structures - /// * unions - /// * optionals - /// * null + /// The type to deserialize into. May not be or contain any of the following types: + /// * Any comptime-only type, except in a comptime field + /// * `type` + /// * `void`, except as a union payload + /// * `noreturn` + /// * An error set/error union + /// * A many-pointer or C-pointer + /// * An opaque type, including `anyopaque` + /// * An async frame type, including `anyframe` and `anyframe->T` + /// * A function + /// + /// All other types are valid. Unsupported types will fail at compile time. T: type, gpa: Allocator, source: [:0]const u8, @@ -1166,11 +1168,11 @@ fn canParseTypeInner(T: type, visited: *[]type, is_comptime: bool) bool { .bool, .int, .float, - .noreturn, .null, .@"enum", => return true, + .noreturn, .void, .type, .undefined, diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index d1bfbc431789..0739d219dcc4 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -6,19 +6,19 @@ //! //! 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 +//! The following types and any types that contain them may not be serialized: +//! * `type` +//! * `void`, except as a union payload +//! * `noreturn` +//! * Error sets/error unions +//! * Untagged unions +//! * Many-pointers or C-pointers +//! * Opaque types, including `anyopaque` +//! * Async frame types, including `anyframe` and `anyframe->T` +//! * Functions //! -//! Unsupported types will fail to serialize at compile time. +//! All other types are valid. Unsupported types will fail to serialize at compile time. Pointers +//! are followed. const std = @import("std"); const assert = std.debug.assert; @@ -155,11 +155,11 @@ fn canSerializeTypeInner(T: type, visited: *[]type) bool { .float, .comptime_float, .comptime_int, - .noreturn, .null, .enum_literal, => return true, + .noreturn, .void, .type, .undefined, diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index bdb00c786c8c..f590b2b3c6e5 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -103,6 +103,8 @@ export fn testMutablePointer() void { // tmp.zig:22:49: note: result type contains type '[*c]const u8' // tmp.zig:27:45: error: c pointers not available in ZON // tmp.zig:27:45: note: result type contains type '[*c]const u8' +// tmp.zig:32:61: error: nested optionals not available in ZON +// tmp.zig:32:61: note: result type contains type '??u8' // tmp.zig:42:29: error: nested optionals not available in ZON // tmp.zig:42:29: note: result type contains type '??u8' // tmp.zig:47:36: error: nested optionals not available in ZON @@ -113,8 +115,6 @@ export fn testMutablePointer() void { // tmp.zig:82:26: note: result type contains type '??f32' // tmp.zig:87:29: error: mutable pointers not available in import ZON // tmp.zig:87:29: note: result type contains type '*i32' -// neg_inf.zon:1:1: error: expected type 'tmp.testComptimeField__struct_471' -// tmp.zig:32:61: note: imported here // neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' // tmp.zig:37:38: note: imported here // neg_inf.zon:1:1: error: expected type '?u8' From f740e82d155e66332cf9854e11bf801568f00a1f Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 15:44:51 +0000 Subject: [PATCH 92/98] some stuff --- lib/std/zon.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zon.zig b/lib/std/zon.zig index 0c7294ab86ad..252331057a8c 100644 --- a/lib/std/zon.zig +++ b/lib/std/zon.zig @@ -40,6 +40,6 @@ pub const parse = @import("zon/parse.zig"); pub const stringify = @import("zon/stringify.zig"); test { - _ = @import("zon/parse.zig"); - _ = @import("zon/stringify.zig"); + _ = parse; + _ = stringify; } From 2b68224d0554e72ace8ebc15b47753f58cf3e356 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 15:45:37 +0000 Subject: [PATCH 93/98] more stuff --- src/Sema.zig | 4 ++-- src/Sema/LowerZon.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 8eb3be64d723..5f4463a9d586 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -187,7 +187,7 @@ const Alignment = InternPool.Alignment; const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; const Cache = std.Build.Cache; -const zon = @import("Sema/LowerZon.zig"); +const LowerZon = @import("Sema/LowerZon.zig"); pub const default_branch_quota = 1000; pub const default_reference_trace_len = 2; @@ -14010,7 +14010,7 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. return sema.fail(block, operand_src, "'@import' of ZON must have a known result type", .{}); } - const interned = try zon.lower( + const interned = try LowerZon.run( sema, result.file, result.file_index, diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index f00f3745c704..f788da277a81 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -29,7 +29,7 @@ block: *Sema.Block, base_node_inst: InternPool.TrackedInst.Index.Optional = .none, /// Lowers the given file as ZON. -pub fn lower( +pub fn run( sema: *Sema, file: *File, file_index: Zcu.File.Index, From 2679275fe743aada628f85c2e1495d2c81398dbc Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 16:41:08 +0000 Subject: [PATCH 94/98] some cleanups and stuff --- lib/std/zon/parse.zig | 137 ++++++++++---------------- lib/std/zon/stringify.zig | 130 +++++++++++------------- src/Sema/LowerZon.zig | 202 +++++++++++++++----------------------- 3 files changed, 189 insertions(+), 280 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index ecb6f6d6188e..5f5f830e2314 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -323,7 +323,7 @@ pub fn fromZoirNode( status: ?*Status, options: Options, ) error{ OutOfMemory, ParseZon }!T { - comptimeAssertCanParseType(T); + comptime assert(canParseType(T)); if (status) |s| { s.assertEmpty(); @@ -733,13 +733,15 @@ const Parser = struct { const field_infos = @typeInfo(T).@"struct".fields; - // Gather info on the fields - const field_indices = comptime b: { + // Build a map from field name to index. + // The special value `comptime_field` indicates that this is actually a comptime field. + const comptime_field = std.math.maxInt(usize); + const field_indices: std.StaticStringMap(usize) = comptime b: { var kvs_list: [field_infos.len]struct { []const u8, usize } = undefined; for (&kvs_list, field_infos, 0..) |*kv, field, i| { - kv.* = .{ field.name, i }; + kv.* = .{ field.name, if (field.is_comptime) comptime_field else i }; } - break :b std.StaticStringMap(usize).initComptime(kvs_list); + break :b .initComptime(kvs_list); }; // Parse the struct @@ -763,13 +765,13 @@ const Parser = struct { // Fill in the fields we found for (0..fields.names.len) |i| { const name = fields.names[i].get(self.zoir); - const field_index = b: { - break :b field_indices.get(name) orelse if (self.options.ignore_unknown_fields) { - continue; - } else { - return self.failUnexpected(T, "field", node, i, name); - }; + const field_index = field_indices.get(name) orelse { + if (self.options.ignore_unknown_fields) continue; + return self.failUnexpected(T, "field", node, i, name); }; + if (field_index == comptime_field) { + return self.failComptimeField(node, i); + } // Mark the field as found. Assert that the found array is not zero length to satisfy // the type checker (it can't be since we made it into an iteration of this loop.) @@ -778,14 +780,12 @@ const Parser = struct { 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, - fields.vals.at(@intCast(i)), - ); - } + if (field_infos[j].is_comptime) unreachable; + + @field(result, field_infos[j].name) = try self.parseExpr( + field_infos[j].type, + fields.vals.at(@intCast(i)), + ); }, else => unreachable, // Can't be out of bounds } @@ -850,7 +850,7 @@ const Parser = struct { }; if (field_infos[i].is_comptime) { - return self.failRuntimeValueComptimeVar(node, i); + return self.failComptimeField(node, i); } else { result[i] = try self.parseExpr(field_infos[i].type, nodes.at(i)); } @@ -1103,7 +1103,7 @@ const Parser = struct { // 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( + fn failComptimeField( self: @This(), node: Zoir.Node.Index, field: usize, @@ -1141,36 +1141,24 @@ fn intFromFloatExact(T: type, value: anytype) ?T { return @intFromFloat(value); } -fn comptimeAssertCanParseType(T: type) void { - if (!comptime canParseType(T)) { - @compileError("cannot parse type '" ++ @typeName(T) ++ "'"); - } -} - fn canParseType(T: type) bool { - comptime { - var visited: []type = &.{}; - return canParseTypeInner(T, &visited, false); - } + comptime return canParseTypeInner(T, &.{}, false); } -fn canParseTypeInner(T: type, visited: *[]type, is_comptime: bool) bool { - // Check if we're a nested optional before we possibly return early due to recursion - if (isNestedOptional(T)) return false; - - // Check/update the visited list to avoid infinite recursion - for (visited.*) |V| if (V == T) return true; - comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; - visited.* = &new_visited; - - // Check if this type is supported, recurse if needed - switch (@typeInfo(T)) { +fn canParseTypeInner( + T: type, + /// Visited structs and unions, to avoid infinite recursion. + /// Tracking more types is unnecessary, and a little complex due to optional nesting. + visited: []const type, + parent_is_optional: bool, +) bool { + return switch (@typeInfo(T)) { .bool, .int, .float, .null, .@"enum", - => return true, + => true, .noreturn, .void, @@ -1182,65 +1170,48 @@ fn canParseTypeInner(T: type, visited: *[]type, is_comptime: bool) bool { .frame, .@"anyframe", .@"opaque", - => return false, - .comptime_int, .comptime_float, .enum_literal, - => return is_comptime, + => false, - .pointer => |pointer| { - switch (pointer.size) { - .one => return canParseTypeInner(pointer.child, visited, is_comptime), - .slice => return canParseTypeInner(pointer.child, visited, is_comptime), - .many, .c => return false, - } + .pointer => |pointer| switch (pointer.size) { + .one => canParseTypeInner(pointer.child, visited, parent_is_optional), + .slice => canParseTypeInner(pointer.child, visited, false), + .many, .c => false, }, - .array => |array| return canParseTypeInner(array.child, visited, is_comptime), + + .optional => |optional| if (parent_is_optional) + false + else + canParseTypeInner(optional.child, visited, true), + + .array => |array| canParseTypeInner(array.child, visited, false), + .vector => |vector| canParseTypeInner(vector.child, visited, false), + .@"struct" => |@"struct"| { - inline for (@"struct".fields) |field| { - if (!canParseTypeInner(field.type, visited, is_comptime or field.is_comptime)) { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"struct".fields) |field| { + if (!field.is_comptime and !canParseTypeInner(field.type, new_visited, false)) { return false; } } return true; }, - .optional => |optional| return canParseTypeInner(optional.child, visited, is_comptime), .@"union" => |@"union"| { - inline for (@"union".fields) |field| { - if (field.type != void and !canParseTypeInner(field.type, visited, is_comptime)) { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"union".fields) |field| { + if (field.type != void and !canParseTypeInner(field.type, new_visited, false)) { return false; } } return true; }, - .vector => |vector| return canParseTypeInner(vector.child, visited, is_comptime), - } -} - -/// Returns true if this type is optional, and it contains another optional either directly or -/// through a series of single item pointers. -fn isNestedOptional(T: type) bool { - comptime switch (@typeInfo(T)) { - .optional => |optional| return isNestedOptionalInner(optional.child), - else => return false, }; } -fn isNestedOptionalInner(T: type) bool { - switch (@typeInfo(T)) { - .pointer => |pointer| { - if (pointer.size == .one) { - return isNestedOptionalInner(pointer.child); - } else { - return false; - } - }, - .optional => return true, - else => return false, - } -} - test "std.zon parse canParseType" { try std.testing.expect(!comptime canParseType(void)); try std.testing.expect(!comptime canParseType(struct { f: [*]u8 })); @@ -1253,7 +1224,7 @@ test "std.zon parse canParseType" { try std.testing.expect(comptime canParseType(union(enum) { foo: void })); try std.testing.expect(!comptime canParseType(comptime_float)); try std.testing.expect(!comptime canParseType(comptime_int)); - try std.testing.expect(!comptime canParseType(struct { comptime foo: ??u8 = null })); + try std.testing.expect(comptime canParseType(struct { comptime foo: ??u8 = null })); try std.testing.expect(!comptime canParseType(@TypeOf(.foo))); try std.testing.expect(comptime canParseType(?u8)); try std.testing.expect(comptime canParseType(*?*u8)); diff --git a/lib/std/zon/stringify.zig b/lib/std/zon/stringify.zig index 0739d219dcc4..a3f2b9cc003c 100644 --- a/lib/std/zon/stringify.zig +++ b/lib/std/zon/stringify.zig @@ -97,59 +97,39 @@ fn typeIsRecursive(comptime T: type) bool { return comptime typeIsRecursiveImpl(T, &.{}); } -fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []type) bool { - // Check if we've already seen this type - inline for (prev_visited) |found| { - if (T == found) { - return true; - } +fn typeIsRecursiveImpl(comptime T: type, comptime prev_visited: []const type) bool { + for (prev_visited) |V| { + if (V == T) return true; } - - // Create a copy of visited with this type added - comptime var visited = prev_visited[0..prev_visited.len].* ++ .{T}; - - // 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; - } - }, + const visited = prev_visited ++ .{T}; + + return switch (@typeInfo(T)) { + .pointer => |pointer| typeIsRecursiveImpl(pointer.child, visited), + .optional => |optional| typeIsRecursiveImpl(optional.child, visited), + .array => |array| typeIsRecursiveImpl(array.child, visited), + .vector => |vector| typeIsRecursiveImpl(vector.child, visited), + .@"struct" => |@"struct"| for (@"struct".fields) |field| { + if (typeIsRecursiveImpl(field.type, visited)) break true; + } else false, .@"union" => |@"union"| inline for (@"union".fields) |field| { - if (typeIsRecursiveImpl(field.type, &visited)) { - return true; - } - }, - .optional => |optional| return typeIsRecursiveImpl(optional.child, &visited), - else => {}, - } - return false; -} - -fn comptimeAssertCanSerializeType(T: type) void { - if (!comptime canSerializeType(T)) { - @compileError("cannot serialize type '" ++ @typeName(T) ++ "'"); - } + if (typeIsRecursiveImpl(field.type, visited)) break true; + } else false, + else => false, + }; } fn canSerializeType(T: type) bool { - var visited: []type = &.{}; - return canSerializeTypeInner(T, &visited); + comptime return canSerializeTypeInner(T, &.{}, false); } -fn canSerializeTypeInner(T: type, visited: *[]type) bool { - // Check if we're a nested optional before we possibly return early due to recursion - if (isNestedOptional(T)) return false; - - // Check/update the visited list to avoid infinite recursion - for (visited.*) |V| if (V == T) return true; - comptime var new_visited = visited.*[0..visited.len].* ++ .{T}; - visited.* = &new_visited; - - // Check if this type is supported, recurse if needed - switch (@typeInfo(T)) { +fn canSerializeTypeInner( + T: type, + /// Visited structs and unions, to avoid infinite recursion. + /// Tracking more types is unnecessary, and a little complex due to optional nesting. + visited: []const type, + parent_is_optional: bool, +) bool { + return switch (@typeInfo(T)) { .bool, .int, .float, @@ -157,7 +137,7 @@ fn canSerializeTypeInner(T: type, visited: *[]type) bool { .comptime_int, .null, .enum_literal, - => return true, + => true, .noreturn, .void, @@ -169,36 +149,44 @@ fn canSerializeTypeInner(T: type, visited: *[]type) bool { .frame, .@"anyframe", .@"opaque", - => return false, + => false, - .@"enum" => |@"enum"| return @"enum".is_exhaustive, + .@"enum" => |@"enum"| @"enum".is_exhaustive, - .pointer => |pointer| { - switch (pointer.size) { - .one => return canSerializeTypeInner(pointer.child, visited), - .slice => return canSerializeTypeInner(pointer.child, visited), - .many, .c => return false, - } + .pointer => |pointer| switch (pointer.size) { + .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional), + .slice => canSerializeTypeInner(pointer.child, visited, false), + .many, .c => false, }, - .array => |array| return canSerializeTypeInner(array.child, visited), + + .optional => |optional| if (parent_is_optional) + false + else + canSerializeTypeInner(optional.child, visited, true), + + .array => |array| canSerializeTypeInner(array.child, visited, false), + .vector => |vector| canSerializeTypeInner(vector.child, visited, false), + .@"struct" => |@"struct"| { - inline for (@"struct".fields) |field| { - if (!canSerializeTypeInner(field.type, visited)) return false; + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; + for (@"struct".fields) |field| { + if (!canSerializeTypeInner(field.type, new_visited, false)) return false; } return true; }, - .optional => |optional| return canSerializeTypeInner(optional.child, visited), .@"union" => |@"union"| { + for (visited) |V| if (T == V) return true; + const new_visited = visited ++ .{T}; if (@"union".tag_type == null) return false; - inline for (@"union".fields) |field| { - if (field.type != void and !canSerializeTypeInner(field.type, visited)) { + for (@"union".fields) |field| { + if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) { return false; } } return true; }, - .vector => |vector| return canSerializeTypeInner(vector.child, visited), - } + }; } fn isNestedOptional(T: type) bool { @@ -481,7 +469,7 @@ pub fn Serializer(Writer: type) type { /// Serialize a value, similar to `serialize`. pub fn value(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); + comptime assert(!typeIsRecursive(@TypeOf(val))); return self.valueArbitraryDepth(val, options); } @@ -502,7 +490,7 @@ pub fn Serializer(Writer: type) type { val: anytype, options: ValueOptions, ) Writer.Error!void { - comptimeAssertCanSerializeType(@TypeOf(val)); + comptime assert(canSerializeType(@TypeOf(val))); switch (@typeInfo(@TypeOf(val))) { .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { self.codePoint(c) catch |err| switch (err) { @@ -666,7 +654,7 @@ pub fn Serializer(Writer: type) type { /// /// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice. pub fn tuple(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); + comptime assert(!typeIsRecursive(@TypeOf(val))); try self.tupleArbitraryDepth(val, options); } @@ -695,7 +683,7 @@ pub fn Serializer(Writer: type) type { } fn tupleImpl(self: *Self, val: anytype, options: ValueOptions) Writer.Error!void { - comptimeAssertCanSerializeType(@TypeOf(val)); + comptime assert(canSerializeType(@TypeOf(val))); switch (@typeInfo(@TypeOf(val))) { .@"struct" => { var container = try self.startTuple(.{ .whitespace_style = .{ .fields = val.len } }); @@ -992,7 +980,7 @@ pub fn Serializer(Writer: type) type { val: anytype, options: ValueOptions, ) Writer.Error!void { - comptimeAssertNoRecursion(@TypeOf(val)); + comptime assert(!typeIsRecursive(@TypeOf(val))); try self.fieldArbitraryDepth(name, val, options); } @@ -1024,12 +1012,6 @@ pub fn Serializer(Writer: type) type { }; } }; - - fn comptimeAssertNoRecursion(comptime T: type) void { - if (comptime typeIsRecursive(T)) { - @compileError(@typeName(T) ++ ": recursive type stringified without depth limit"); - } - } }; } diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index f788da277a81..d2734c2391b0 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -26,7 +26,7 @@ file: *File, file_index: Zcu.File.Index, import_loc: LazySrcLoc, block: *Sema.Block, -base_node_inst: InternPool.TrackedInst.Index.Optional = .none, +base_node_inst: InternPool.TrackedInst.Index, /// Lowers the given file as ZON. pub fn run( @@ -37,13 +37,22 @@ pub fn run( import_loc: LazySrcLoc, block: *Sema.Block, ) CompileError!InternPool.Index { - _ = try file.getZoir(sema.pt.zcu); + const pt = sema.pt; + + _ = try file.getZoir(pt.zcu); + + const tracked_inst = try pt.zcu.intern_pool.trackZir(pt.zcu.gpa, pt.tid, .{ + .file = file_index, + .inst = .main_struct_inst, // this is the only trackable instruction in a ZON file + }); + var lower_zon: LowerZon = .{ .sema = sema, .file = file, .file_index = file_index, .import_loc = import_loc, .block = block, + .base_node_inst = tracked_inst, }; try lower_zon.checkType(res_ty); @@ -51,43 +60,29 @@ pub fn run( return lower_zon.lowerExpr(.root, res_ty); } +/// Validate that `ty` is a valid ZON type. If not, emit a compile error. +/// i.e. no nested optionals, no error sets, etc. fn checkType(self: *LowerZon, ty: Type) !void { - var visited: std.AutoHashMapUnmanaged(Type, void) = .empty; - try self.checkTypeInner(ty, &visited); + var visited: std.AutoHashMapUnmanaged(InternPool.Index, void) = .empty; + try self.checkTypeInner(ty, null, &visited); } fn checkTypeInner( self: *LowerZon, ty: Type, + parent_opt_ty: ?Type, + /// Visited structs and unions (not tuples). These are tracked because they are the only way in + /// which a type can be self-referential, so must be tracked to avoid loops. Tracking more types + /// consumes memory unnecessarily, and would be complicated by optionals. + /// Allocated into `self.sema.arena`. visited: *std.AutoHashMapUnmanaged(Type, void), ) !void { - const pt = self.sema.pt; - - // Check if we're a nested optional before we possibly return early due to recursion - if (ty.zigTypeTag(pt.zcu) == .optional) { - var curr = ty.optionalChild(pt.zcu); - while (true) { - switch (curr.zigTypeTag(pt.zcu)) { - .optional => return self.failUnsupportedResultType( - "nested optionals not available in ZON", - ty, - ), - .pointer => { - const ptr_info = curr.ptrInfo(pt.zcu); - if (ptr_info.flags.size != .one) break; - curr = .fromInterned(ptr_info.child); - }, - else => break, - } - } - } - - // Check/update the visited list to avoid infinite recursion - const gop = try visited.getOrPut(pt.zcu.gpa, ty); - if (gop.found_existing) return; + const sema = self.sema; + const pt = sema.pt; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; - // Check if this type is supported, recurse if needed - switch (ty.zigTypeTag(pt.zcu)) { + switch (ty.zigTypeTag(zcu)) { .bool, .int, .float, @@ -108,99 +103,92 @@ fn checkTypeInner( .frame, .@"anyframe", .@"opaque", - => return self.failUnsupportedResultType( - "result type not available in ZON", - ty, - ), + => return self.failUnsupportedResultType(ty, null), .pointer => { - const ptr_info = ty.ptrInfo(pt.zcu); + const ptr_info = ty.ptrInfo(zcu); if (!ptr_info.flags.is_const) { return self.failUnsupportedResultType( - "mutable pointers not available in import ZON", ty, + "ZON does not allow mutable pointers", ); } switch (ptr_info.flags.size) { .one => try self.checkTypeInner( .fromInterned(ptr_info.child), + parent_opt_ty, // preserved visited, ), .slice => try self.checkTypeInner( .fromInterned(ptr_info.child), + null, visited, ), - .many => return self.failUnsupportedResultType( - "many item pointers not available in ZON", - ty, - ), - .c => return self.failUnsupportedResultType("c pointers not available in ZON", ty), + .many => return self.failUnsupportedResultType(ty, "ZON does not allow many-pointers"), + .c => return self.failUnsupportedResultType(ty, "ZON does not allow C pointers"), } }, - .array => { - const array_info = ty.arrayInfo(pt.zcu); - try self.checkTypeInner(array_info.elem_type, visited); + .optional => if (parent_opt_ty) |p| { + return self.failUnsupportedResultType(p, "ZON does not allow nested optionals"); + } else try self.checkTypeInner( + ty.optionalChild(zcu), + ty, + visited, + ), + .array, .vector => { + try self.checkTypeInner(ty.childType(zcu), null, visited); }, - .@"struct" => { - if (ty.isTuple(pt.zcu)) { - const tuple_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).tuple_type; - const field_types = tuple_info.types.get(&pt.zcu.intern_pool); - for (field_types) |field_type| { - try self.checkTypeInner(.fromInterned(field_type), visited); - } - } else { - try ty.resolveFields(pt); - const struct_info = pt.zcu.typeToStruct(ty).?; - for (struct_info.field_types.get(&pt.zcu.intern_pool)) |field_type| { - try self.checkTypeInner(.fromInterned(field_type), visited); - } + .@"struct" => if (ty.isTuple(zcu)) { + const tuple_info = ip.indexToKey(ty.toIntern()).tuple_type; + const field_types = tuple_info.types.get(ip); + for (field_types) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), null, visited); + } + } else { + const gop = try visited.getOrPut(sema.arena, ty.toIntern()); + if (gop.found_existing) return; + try ty.resolveFields(pt); + const struct_info = zcu.typeToStruct(ty).?; + for (struct_info.field_types.get(ip)) |field_type| { + try self.checkTypeInner(.fromInterned(field_type), null, visited); } }, - .optional => try self.checkTypeInner(ty.optionalChild(pt.zcu), visited), .@"union" => { + const gop = try visited.getOrPut(sema.arena, ty.toIntern()); + if (gop.found_existing) return; try ty.resolveFields(pt); - const union_info = pt.zcu.typeToUnion(ty).?; - for (union_info.field_types.get(&pt.zcu.intern_pool)) |field_type| { + const union_info = zcu.typeToUnion(ty).?; + for (union_info.field_types.get(ip)) |field_type| { if (field_type != .void_type) { - try self.checkTypeInner(.fromInterned(field_type), visited); + try self.checkTypeInner(.fromInterned(field_type), null, visited); } } }, - .vector => { - const vector_info = pt.zcu.intern_pool.indexToKey(ty.toIntern()).vector_type; - try self.checkTypeInner(.fromInterned(vector_info.child), visited); - }, } } -fn lazySrcLoc(self: *LowerZon, loc: LazySrcLoc.Offset) !LazySrcLoc { - if (self.base_node_inst == .none) { - self.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 }, - )).toOptional(); - } +fn nodeSrc(self: *LowerZon, node: Zoir.Node.Index) LazySrcLoc { return .{ - .base_node_inst = self.base_node_inst.unwrap().?, - .offset = loc, + .base_node_inst = self.base_node_inst, + .offset = .{ .node_abs = node.getAstNode(self.file.zoir.?) }, }; } fn failUnsupportedResultType( self: *LowerZon, - msg: []const u8, ty: Type, + opt_note: ?[]const u8, ) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); - const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.import_loc, "{s}", .{msg}); - try self.sema.pt.zcu.errNote( - self.import_loc, - err_msg, - "result type contains type '{}'", - .{ty.fmt(self.sema.pt)}, - ); - return self.sema.failWithOwnedErrorMsg(self.block, err_msg); + const sema = self.sema; + const gpa = sema.gpa; + const pt = sema.pt; + return sema.failWithOwnedErrorMsg(self.block, msg: { + const msg = try sema.errMsg(self.import_loc, "type '{}' is not available in ZON", .{ty.fmt(pt)}); + errdefer msg.destroy(gpa); + if (opt_note) |n| try sema.errNote(self.import_loc, msg, "{s}", .{n}); + break :msg msg; + }); } fn fail( @@ -210,8 +198,7 @@ fn fail( args: anytype, ) error{ AnalysisFail, OutOfMemory } { @branchHint(.cold); - const src_loc = try self.lazySrcLoc(.{ .node_abs = node.getAstNode(self.file.zoir.?) }); - const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, src_loc, format, args); + const err_msg = try Zcu.ErrorMsg.create(self.sema.pt.zcu.gpa, self.nodeSrc(node), format, args); try self.sema.pt.zcu.errNote(self.import_loc, err_msg, "imported here", .{}); return self.sema.failWithOwnedErrorMsg(self.block, err_msg); } @@ -219,42 +206,11 @@ fn fail( fn lowerExpr(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) CompileError!InternPool.Index { const pt = self.sema.pt; return self.lowerExprInner(node, res_ty) catch |err| switch (err) { - error.WrongType => { - // Flatten to a type directly compatible with ZON before reporting the error - var display_ty = res_ty; - var opt = false; - - while (true) { - switch (display_ty.zigTypeTag(pt.zcu)) { - .optional => { - opt = true; - display_ty = display_ty.childType(pt.zcu); - }, - .pointer => { - const ptr_info = res_ty.ptrInfo(pt.zcu); - if (ptr_info.flags.size == .one) { - display_ty = .fromInterned(ptr_info.child); - } else { - break; - } - }, - else => break, - } - } - - if (opt) { - display_ty = .fromInterned(try pt.intern(.{ - .opt_type = display_ty.toIntern(), - })); - } - - // Report the error - return self.fail( - node, - "expected type '{}'", - .{display_ty.fmt(self.sema.pt)}, - ); - }, + error.WrongType => return self.fail( + node, + "expected type '{}'", + .{res_ty.fmt(pt)}, + ), else => |e| return e, }; } @@ -744,7 +700,7 @@ fn lowerSlice(self: *LowerZon, node: Zoir.Node.Index, res_ty: Type) !InternPool. self.block, res_ty, str_ref, - try self.lazySrcLoc(.{ .node_abs = node.getAstNode(self.file.zoir.?) }), + self.nodeSrc(node), )).toInterned().?; }, else => {}, From 5d967d68c2ceb42f25caf5b2ecc8b1e479491b8a Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 16:46:30 +0000 Subject: [PATCH 95/98] fix ci failures (cbe bugs) --- lib/std/zon/parse.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index 5f5f830e2314..b215754849ca 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -8,6 +8,7 @@ //! For lower level control, it is possible to operate on `std.zig.Zoir` directly. const std = @import("std"); +const builtin = @import("builtin"); const Allocator = std.mem.Allocator; const Ast = std.zig.Ast; const Zoir = std.zig.Zoir; @@ -1822,8 +1823,7 @@ test "std.zon tuples" { // 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; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/20881 const gpa = std.testing.allocator; @@ -3083,6 +3083,8 @@ test "std.zon free on error" { } test "std.zon vector" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330 + const gpa = std.testing.allocator; // Passing cases From 2f5fccbd75360c8ff7cbd737ebd1794887abf7a9 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 16:48:05 +0000 Subject: [PATCH 96/98] fix a typo from earlier --- src/Sema/LowerZon.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sema/LowerZon.zig b/src/Sema/LowerZon.zig index d2734c2391b0..2b2d16b90a09 100644 --- a/src/Sema/LowerZon.zig +++ b/src/Sema/LowerZon.zig @@ -75,7 +75,7 @@ fn checkTypeInner( /// which a type can be self-referential, so must be tracked to avoid loops. Tracking more types /// consumes memory unnecessarily, and would be complicated by optionals. /// Allocated into `self.sema.arena`. - visited: *std.AutoHashMapUnmanaged(Type, void), + visited: *std.AutoHashMapUnmanaged(InternPool.Index, void), ) !void { const sema = self.sema; const pt = sema.pt; From be12e2d4984f9cac87ea5fb2477a999ff36fadd0 Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 17:18:55 +0000 Subject: [PATCH 97/98] fix cases i broke --- .../compile_errors/@import_zon_bad_type.zig | 52 +++++++++---------- .../compile_errors/@import_zon_opt_in_err.zig | 27 ++++------ test/cases/compile_errors/redundant_try.zig | 4 +- 3 files changed, 37 insertions(+), 46 deletions(-) diff --git a/test/cases/compile_errors/@import_zon_bad_type.zig b/test/cases/compile_errors/@import_zon_bad_type.zig index f590b2b3c6e5..4942b570728f 100644 --- a/test/cases/compile_errors/@import_zon_bad_type.zig +++ b/test/cases/compile_errors/@import_zon_bad_type.zig @@ -91,37 +91,35 @@ export fn testMutablePointer() void { // error // imports=zon/neg_inf.zon // -// tmp.zig:2:29: error: result type not available in ZON -// tmp.zig:2:29: note: result type contains type 'void' -// tmp.zig:7:50: error: many item pointers not available in ZON -// tmp.zig:7:50: note: result type contains type '[*]const u8' -// tmp.zig:12:46: error: result type not available in ZON -// tmp.zig:12:46: note: result type contains type 'error{foo}' -// tmp.zig:17:65: error: c pointers not available in ZON -// tmp.zig:17:65: note: result type contains type '[*c]const u8' -// tmp.zig:22:49: error: c pointers not available in ZON -// tmp.zig:22:49: note: result type contains type '[*c]const u8' -// tmp.zig:27:45: error: c pointers not available in ZON -// tmp.zig:27:45: note: result type contains type '[*c]const u8' -// tmp.zig:32:61: error: nested optionals not available in ZON -// tmp.zig:32:61: note: result type contains type '??u8' -// tmp.zig:42:29: error: nested optionals not available in ZON -// tmp.zig:42:29: note: result type contains type '??u8' -// tmp.zig:47:36: error: nested optionals not available in ZON -// tmp.zig:47:36: note: result type contains type '?*const ?u8' -// tmp.zig:52:50: error: nested optionals not available in ZON -// tmp.zig:52:50: note: result type contains type '?*const ?*const u8' -// tmp.zig:82:26: error: nested optionals not available in ZON -// tmp.zig:82:26: note: result type contains type '??f32' -// tmp.zig:87:29: error: mutable pointers not available in import ZON -// tmp.zig:87:29: note: result type contains type '*i32' +// tmp.zig:2:29: error: type 'void' is not available in ZON +// tmp.zig:7:50: error: type '[*]const u8' is not available in ZON +// tmp.zig:7:50: note: ZON does not allow many-pointers +// tmp.zig:12:46: error: type 'error{foo}' is not available in ZON +// tmp.zig:17:65: error: type '[*c]const u8' is not available in ZON +// tmp.zig:17:65: note: ZON does not allow C pointers +// tmp.zig:22:49: error: type '[*c]const u8' is not available in ZON +// tmp.zig:22:49: note: ZON does not allow C pointers +// tmp.zig:27:45: error: type '[*c]const u8' is not available in ZON +// tmp.zig:27:45: note: ZON does not allow C pointers +// tmp.zig:32:61: error: type '??u8' is not available in ZON +// tmp.zig:32:61: note: ZON does not allow nested optionals +// tmp.zig:42:29: error: type '??u8' is not available in ZON +// tmp.zig:42:29: note: ZON does not allow nested optionals +// tmp.zig:47:36: error: type '?*const ?u8' is not available in ZON +// tmp.zig:47:36: note: ZON does not allow nested optionals +// tmp.zig:52:50: error: type '?*const ?*const u8' is not available in ZON +// tmp.zig:52:50: note: ZON does not allow nested optionals +// tmp.zig:82:26: error: type '??f32' is not available in ZON +// tmp.zig:82:26: note: ZON does not allow nested optionals +// tmp.zig:87:29: error: type '*i32' is not available in ZON +// tmp.zig:87:29: note: ZON does not allow mutable pointers // neg_inf.zon:1:1: error: expected type '@Type(.enum_literal)' // tmp.zig:37:38: note: imported here // neg_inf.zon:1:1: error: expected type '?u8' // tmp.zig:57:28: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_489' +// neg_inf.zon:1:1: error: expected type 'tmp.testNonExhaustiveEnum__enum_490' // tmp.zig:62:39: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_491' +// neg_inf.zon:1:1: error: expected type 'tmp.testUntaggedUnion__union_492' // tmp.zig:67:44: note: imported here -// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_494' +// neg_inf.zon:1:1: error: expected type 'tmp.testTaggedUnionVoid__union_495' // tmp.zig:72:50: note: imported here diff --git a/test/cases/compile_errors/@import_zon_opt_in_err.zig b/test/cases/compile_errors/@import_zon_opt_in_err.zig index 3f068345fc40..b7322a43b421 100644 --- a/test/cases/compile_errors/@import_zon_opt_in_err.zig +++ b/test/cases/compile_errors/@import_zon_opt_in_err.zig @@ -13,11 +13,6 @@ export fn testFloatC() void { _ = f; } -export fn testFloatD() void { - const f: ?*const f32 = @import("zon/vec2.zon"); - _ = f; -} - export fn testBool() void { const f: ?bool = @import("zon/vec2.zon"); _ = f; @@ -65,25 +60,23 @@ export fn testVector() void { // // vec2.zon:1:2: error: expected type '?f32' // tmp.zig:2:29: note: imported here -// vec2.zon:1:2: error: expected type '?f32' +// vec2.zon:1:2: error: expected type '*const ?f32' // tmp.zig:7:36: note: imported here -// vec2.zon:1:2: error: expected type '?f32' +// vec2.zon:1:2: error: expected type '?*const f32' // tmp.zig:12:36: note: imported here -// vec2.zon:1:2: error: expected type '?f32' -// tmp.zig:17:36: note: imported here // vec2.zon:1:2: error: expected type '?bool' -// tmp.zig:22:30: note: imported here +// tmp.zig:17:30: note: imported here // vec2.zon:1:2: error: expected type '?i32' -// tmp.zig:27:29: note: imported here +// tmp.zig:22:29: note: imported here // vec2.zon:1:2: error: expected type '?tmp.Enum' -// tmp.zig:33:30: note: imported here +// tmp.zig:28:30: note: imported here // vec2.zon:1:2: error: expected type '?@Type(.enum_literal)' -// tmp.zig:38:39: note: imported here +// tmp.zig:33:39: note: imported here // vec2.zon:1:2: error: expected type '?[1]u8' -// tmp.zig:43:31: note: imported here +// tmp.zig:38:31: note: imported here // vec2.zon:1:2: error: expected type '?tmp.Union' -// tmp.zig:49:31: note: imported here +// tmp.zig:44:31: note: imported here // vec2.zon:1:2: error: expected type '?[]const u8' -// tmp.zig:54:36: note: imported here +// tmp.zig:49:36: note: imported here // vec2.zon:1:2: error: expected type '?@Vector(3, f32)' -// tmp.zig:59:41: note: imported here +// tmp.zig:54:41: note: imported here diff --git a/test/cases/compile_errors/redundant_try.zig b/test/cases/compile_errors/redundant_try.zig index 22dabb1bdbd6..7d02bbd96711 100644 --- a/test/cases/compile_errors/redundant_try.zig +++ b/test/cases/compile_errors/redundant_try.zig @@ -44,9 +44,9 @@ comptime { // // :5:23: error: expected error union type, found 'comptime_int' // :10:23: error: expected error union type, found '@TypeOf(.{})' -// :15:23: error: expected error union type, found 'tmp.test2__struct_493' +// :15:23: error: expected error union type, found 'tmp.test2__struct_494' // :15:23: note: struct declared here -// :20:27: error: expected error union type, found 'tmp.test3__struct_495' +// :20:27: error: expected error union type, found 'tmp.test3__struct_496' // :20:27: note: struct declared here // :25:23: error: expected error union type, found 'struct { comptime *const [5:0]u8 = "hello" }' // :31:13: error: expected error union type, found 'u32' From c9385931ab876be9afdbd00b2898320a493bccee Mon Sep 17 00:00:00 2001 From: mlugg Date: Sun, 2 Feb 2025 21:54:15 +0000 Subject: [PATCH 98/98] ci --- lib/std/zon/parse.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/zon/parse.zig b/lib/std/zon/parse.zig index b215754849ca..daf83d0bbdb0 100644 --- a/lib/std/zon/parse.zig +++ b/lib/std/zon/parse.zig @@ -3084,6 +3084,7 @@ test "std.zon free on error" { test "std.zon vector" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15330 + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/15329 const gpa = std.testing.allocator;