authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-07-17 03:23:14-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-09-26 12:35:38-07:00
log09e69d88ed0e3b8cf3c65aec4559614a835021a9
tree4d328364dcfe4e8918209c4b904d0640ef6c1464
parent9ef02702cebc0de136384f0b7dc9d19aff13f27b

`updateFrom*` functions no longer require an arena


1 files changed, 115 insertions(+), 71 deletions(-)

lib/std/zon/parse.zig+115-71
......@@ -42,6 +42,7 @@ pub const FromOptions = struct {
4242 .ignore_unknown_fields = self.ignore_unknown_fields,
4343 .ignore_missing_fields = false,
4444 .free_on_error = self.free_on_error,
45 .free_on_write = false,
4546 };
4647 }
4748};
......@@ -50,12 +51,17 @@ pub const FromOptions = struct {
5051pub const UpdateFromOptions = struct {
5152 /// If true, unknown fields do not error.
5253 ignore_unknown_fields: bool = false,
54 /// If true, the old values of overriden fields are freed. You may want to turn this off if you
55 /// are updating a value that shouldn't be freed e.g. because it contains Zig string literals.
56 /// In this case, you can use arena allocation.
57 free_on_write: bool = true,
5358
5459 fn toParserOptions(self: UpdateFromOptions) Parser.Options {
5560 return .{
5661 .ignore_unknown_fields = self.ignore_unknown_fields,
5762 .ignore_missing_fields = true,
5863 .free_on_error = false,
64 .free_on_write = true,
5965 };
6066 }
6167};
......@@ -313,7 +319,8 @@ pub fn fromSlice(
313319}
314320
315321/// Similar to `fromSlice`, but updates an existing value `current`. Fields not specified by ZON are
316/// left at their current values. Pointers are replaced with new values not mutated in place.
322/// left at their current values. Pointers are replaced with new values not mutated in place. On
323/// failure, values that were updated prior to the failure are left updated.
317324pub fn updateFromSlice(
318325 T: type,
319326 gpa: Allocator,
......@@ -340,39 +347,39 @@ pub fn fromSliceAlloc(
340347 return result;
341348}
342349
343/// Like updateFromSlice`, but the result may contain pointers. Requires an arena for cleanup.
350/// Like updateFromSlice`, but the result may contain pointers.
344351pub fn updateFromSliceAlloc(
345352 T: type,
346 arena: Allocator,
353 gpa: Allocator,
347354 current: *T,
348355 source: [:0]const u8,
349356 diag: ?*Diagnostics,
350357 options: UpdateFromOptions,
351358) error{ OutOfMemory, ParseZon }!void {
352 try parseSliceAlloc(T, current, arena, source, diag, options.toParserOptions());
359 try parseSliceAlloc(T, current, gpa, source, diag, options.toParserOptions());
353360}
354361
355362fn parseSliceAlloc(
356363 T: type,
357364 current: *T,
358 arena: Allocator,
365 gpa: Allocator,
359366 source: [:0]const u8,
360367 diag: ?*Diagnostics,
361368 options: Parser.Options,
362369) error{ OutOfMemory, ParseZon }!void {
363370 if (diag) |s| s.assertEmpty();
364371
365 var ast = try std.zig.Ast.parse(arena, source, .{ .mode = .zon });
366 defer if (diag == null) ast.deinit(arena);
372 var ast = try std.zig.Ast.parse(gpa, source, .{ .mode = .zon });
373 defer if (diag == null) ast.deinit(gpa);
367374 if (diag) |s| s.ast = ast;
368375
369376 // If there's no diagnostics, Zoir exists for the lifetime of this function. If there is a
370377 // diagnostics, ownership is transferred to diagnostics.
371 var zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
372 defer if (diag == null) zoir.deinit(arena);
378 var zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
379 defer if (diag == null) zoir.deinit(gpa);
373380
374381 if (diag) |s| s.* = .{};
375 try parseZoirAlloc(T, arena, current, ast, zoir, diag, options);
382 try parseZoirAlloc(T, gpa, current, ast, zoir, diag, options);
376383}
377384
378385/// Like `fromSlice`, but operates on `Zoir` instead of ZON source.
......@@ -440,14 +447,14 @@ pub fn fromZoirAlloc(
440447/// Like updateSliceAlloc`, but operates on `Zoir` instead of ZON source.
441448pub fn updateFromZoirAlloc(
442449 T: type,
443 arena: Allocator,
450 gpa: Allocator,
444451 current: *T,
445452 ast: Ast,
446453 zoir: Zoir,
447454 diag: ?*Diagnostics,
448455 options: UpdateFromOptions,
449456) error{ OutOfMemory, ParseZon }!void {
450 try parseZoirAlloc(T, arena, current, ast, zoir, diag, options.toParserOptions());
457 try parseZoirAlloc(T, gpa, current, ast, zoir, diag, options.toParserOptions());
451458}
452459
453460fn parseZoirAlloc(
......@@ -534,7 +541,7 @@ pub fn fromZoirNodeAlloc(
534541/// Like `updateZoirNodeAlloc`, but the parse starts at `node` instead of root.
535542pub fn updateFromZoirNodeAlloc(
536543 T: type,
537 arena: Allocator,
544 gpa: Allocator,
538545 current: *T,
539546 ast: Ast,
540547 zoir: Zoir,
......@@ -542,7 +549,7 @@ pub fn updateFromZoirNodeAlloc(
542549 diag: ?*Diagnostics,
543550 options: UpdateFromOptions,
544551) error{ OutOfMemory, ParseZon }!void {
545 return parseZoirNodeAlloc(T, arena, current, ast, zoir, node, diag, options.toParserOptions());
552 return parseZoirNodeAlloc(T, gpa, current, ast, zoir, node, diag, options.toParserOptions());
546553}
547554
548555fn parseZoirNodeAlloc(
......@@ -667,6 +674,7 @@ const Parser = struct {
667674 pub const Options = struct {
668675 ignore_unknown_fields: bool,
669676 free_on_error: bool,
677 free_on_write: bool,
670678 ignore_missing_fields: bool,
671679 };
672680
......@@ -687,21 +695,22 @@ const Parser = struct {
687695 out: anytype,
688696 ) ParseExprInnerError!void {
689697 if (@TypeOf(out.*) == Zoir.Node.Index) {
690 out.* = node;
698 self.write(out, node);
691699 return;
692700 }
693701
694702 switch (@typeInfo(@TypeOf(out.*))) {
695703 .optional => |optional| if (node.get(self.zoir) == .null) {
696 out.* = null;
704 self.write(out, null);
697705 } else {
698 out.* = @as(optional.child, undefined); // Make non-null
699 try self.parseExprInnerInto(node, &out.*.?); // Parse value into non-null pointer
706 var new: optional.child = undefined;
707 try self.parseExprInnerInto(node, &new);
708 self.write(out, new);
700709 },
701 .bool => out.* = try self.parseBool(node),
702 .int => out.* = try self.parseInt(@TypeOf(out.*), node),
703 .float => out.* = try self.parseFloat(@TypeOf(out.*), node),
704 .@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node),
710 .bool => self.write(out, try self.parseBool(node)),
711 .int => self.write(out, try self.parseInt(@TypeOf(out.*), node)),
712 .float => self.write(out, try self.parseFloat(@TypeOf(out.*), node)),
713 .@"enum" => self.write(out, try self.parseEnumLiteral(@TypeOf(out.*), node)),
705714 .pointer => |pointer| switch (pointer.size) {
706715 .one => {
707716 const slice = try self.gpa.alignedAlloc(
......@@ -710,13 +719,23 @@ const Parser = struct {
710719 1,
711720 );
712721 errdefer self.gpa.free(slice);
713 const ptr = &slice[0];
714 self.pointer_depth += 1;
715 defer self.pointer_depth -= 1;
716 try self.parseExprInnerInto(node, ptr);
717 out.* = ptr;
722 const new = &slice[0];
723 {
724 self.pointer_depth += 1;
725 defer self.pointer_depth -= 1;
726 try self.parseExprInnerInto(node, new);
727 }
728 self.write(out, new);
729 },
730 .slice => {
731 const new = b: {
732 self.pointer_depth += 1;
733 defer self.pointer_depth -= 1;
734 const new = try self.parseSlicePointer(@TypeOf(out.*), node);
735 break :b new;
736 };
737 self.write(out, new);
718738 },
719 .slice => out.* = try self.parseSlicePointer(@TypeOf(out.*), node),
720739 else => comptime unreachable,
721740 },
722741 .array => try self.parseArrayInto(node, out),
......@@ -994,7 +1013,7 @@ const Parser = struct {
9941013 const vector = @typeInfo(@TypeOf(out.*)).vector;
9951014 var array: [vector.len]vector.child = undefined;
9961015 try self.parseArrayInto(node, &array);
997 out.* = array;
1016 self.write(out, array);
9981017 }
9991018
10001019 fn parseStructInto(self: *@This(), node: Zoir.Node.Index, out: anytype) !void {
......@@ -1166,7 +1185,7 @@ const Parser = struct {
11661185 return self.failNode(node, "expected union");
11671186
11681187 // Instantiate the union
1169 out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], {});
1188 self.write(out, @unionInit(@TypeOf(out.*), @"union".field_names[i], {}));
11701189 },
11711190 else => unreachable, // Can't be out of bounds
11721191 }
......@@ -1188,7 +1207,7 @@ const Parser = struct {
11881207 if (@"union".field_types[i] == void) {
11891208 return self.failNode(field_val, "expected type 'void'");
11901209 } else {
1191 out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], undefined);
1210 self.write(out, @unionInit(@TypeOf(out.*), @"union".field_names[i], undefined));
11921211 try self.parseExprInto(field_val, &@field(out.*, @"union".field_names[i]));
11931212 }
11941213 },
......@@ -1363,6 +1382,16 @@ const Parser = struct {
13631382 .note = null,
13641383 });
13651384 }
1385
1386 /// Write to out, freeing the old value first if necessary.
1387 fn write(self: *@This(), out: anytype, new: @TypeOf(out.*)) void {
1388 if (@typeInfo(@TypeOf(out)) == .pointer) {
1389 if (self.options.free_on_write and self.pointer_depth == 0) {
1390 free(self.gpa, out.*);
1391 }
1392 }
1393 out.* = new;
1394 }
13661395};
13671396
13681397fn intFromFloatExact(T: type, value: anytype) ?T {
......@@ -3785,12 +3814,8 @@ test "std.zon aligned pointers" {
37853814 try std.testing.expectEqualDeep(expected.inner, found.inner);
37863815}
37873816
3788test "std.zon update semantics" {
3789 // Test that the update logic works as expected
3790
3791 var arena_allocator: std.heap.ArenaAllocator = .init(std.testing.allocator);
3792 defer arena_allocator.deinit();
3793 const arena = arena_allocator.allocator();
3817test "std.zon update" {
3818 const gpa = std.testing.allocator;
37943819
37953820 const Vector = struct { x: f32, y: f32, z: f32 };
37963821 const MyStruct = struct {
......@@ -3802,42 +3827,58 @@ test "std.zon update semantics" {
38023827 },
38033828 ptr: *Vector,
38043829 optional: ?u32 = null,
3805 str: []const u8 = "hello, world",
3830 str: []const u8,
38063831 };
38073832
3808 var v: Vector = .{ .x = 1, .y = 2, .z = 3 };
38093833 var expected: MyStruct = .{
38103834 .foo = 10,
38113835 .bar = true,
38123836 .baz = .{ .a = .{ 1, 2 }, .b = .c },
3813 .ptr = &v,
3837 .ptr = b: {
3838 const v = gpa.create(Vector) catch @panic("oom");
3839 v.* = .{ .x = 1, .y = 2, .z = 3 };
3840 break :b v;
3841 },
3842 .str = gpa.dupe(u8, "hello, world") catch @panic("oom"),
3843 };
3844 defer free(gpa, expected);
3845 var found: MyStruct = .{
3846 .foo = 10,
3847 .bar = true,
3848 .baz = .{ .a = .{ 1, 2 }, .b = .c },
3849 .ptr = b: {
3850 const v = gpa.create(Vector) catch @panic("oom");
3851 v.* = .{ .x = 1, .y = 2, .z = 3 };
3852 break :b v;
3853 },
3854 .str = gpa.dupe(u8, "hello, world") catch @panic("oom"),
38143855 };
3815 var found = expected;
3856 defer free(gpa, found);
38163857
38173858 try updateFromSliceAlloc(
38183859 MyStruct,
3819 arena,
3860 gpa,
38203861 &found,
38213862 ".{}",
38223863 null,
38233864 .{},
38243865 );
3825 try std.testing.expectEqual(expected, found);
3866 try std.testing.expectEqualDeep(expected, found);
38263867
38273868 try updateFromSliceAlloc(
38283869 MyStruct,
3829 arena,
3870 gpa,
38303871 &found,
38313872 ".{ .bar = false }",
38323873 null,
38333874 .{},
38343875 );
38353876 expected.bar = false;
3836 try std.testing.expectEqual(expected, found);
3877 try std.testing.expectEqualDeep(expected, found);
38373878
38383879 try updateFromSliceAlloc(
38393880 MyStruct,
3840 arena,
3881 gpa,
38413882 &found,
38423883 ".{ .baz = .{ .a = .{ 2, 4 } } }",
38433884 null,
......@@ -3845,11 +3886,11 @@ test "std.zon update semantics" {
38453886 );
38463887 expected.baz.a[0] = 2;
38473888 expected.baz.a[1] = 4;
3848 try std.testing.expectEqual(expected, found);
3889 try std.testing.expectEqualDeep(expected, found);
38493890
38503891 try updateFromSliceAlloc(
38513892 MyStruct,
3852 arena,
3893 gpa,
38533894 &found,
38543895 ".{ .foo = 11, .baz = .{ .b = .d } }",
38553896 null,
......@@ -3857,33 +3898,33 @@ test "std.zon update semantics" {
38573898 );
38583899 expected.foo = 11;
38593900 expected.baz.b = .d;
3860 try std.testing.expectEqual(expected, found);
3901 try std.testing.expectEqualDeep(expected, found);
38613902
38623903 try updateFromSliceAlloc(
38633904 MyStruct,
3864 arena,
3905 gpa,
38653906 &found,
38663907 ".{ .optional = 10 }",
38673908 null,
38683909 .{},
38693910 );
38703911 expected.optional = 10;
3871 try std.testing.expectEqual(expected, found);
3912 try std.testing.expectEqualDeep(expected, found);
38723913
38733914 try updateFromSliceAlloc(
38743915 MyStruct,
3875 arena,
3916 gpa,
38763917 &found,
38773918 ".{ .optional = null }",
38783919 null,
38793920 .{},
38803921 );
38813922 expected.optional = null;
3882 try std.testing.expectEqual(expected, found);
3923 try std.testing.expectEqualDeep(expected, found);
38833924
38843925 try updateFromSliceAlloc(
38853926 MyStruct,
3886 arena,
3927 gpa,
38873928 &found,
38883929 ".{ .ptr = .{ .x = 10, .y = 20, .z = 30 } }",
38893930 null,
......@@ -3896,13 +3937,14 @@ test "std.zon update semantics" {
38963937
38973938 try updateFromSliceAlloc(
38983939 MyStruct,
3899 arena,
3940 gpa,
39003941 &found,
39013942 ".{ .str = \"foo\" }",
39023943 null,
39033944 .{},
39043945 );
3905 expected.str = "foo";
3946 gpa.free(expected.str);
3947 expected.str = gpa.dupe(u8, "foo") catch @panic("oom");
39063948 try std.testing.expectEqualDeep(expected, found);
39073949}
39083950
......@@ -3910,9 +3952,7 @@ test "std.zon variants" {
39103952 // Just a smoke test, we don't need to test each variant thoroughly because they all call into
39113953 // the same code.
39123954
3913 var arena_allocator: std.heap.ArenaAllocator = .init(std.testing.allocator);
3914 defer arena_allocator.deinit();
3915 const arena = arena_allocator.allocator();
3955 const gpa = std.testing.allocator;
39163956
39173957 const Struct = struct { a: u32, b: u32 };
39183958 const start: Struct = .{ .a = 10, .b = 20 };
......@@ -3920,15 +3960,17 @@ test "std.zon variants" {
39203960
39213961 // Update
39223962 {
3923 const ast = try std.zig.Ast.parse(arena, ".{ .a = 100 }", .{ .mode = .zon });
3924 const zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
3963 var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100 }", .{ .mode = .zon });
3964 defer ast.deinit(gpa);
3965 const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
3966 defer zoir.deinit(gpa);
39253967
39263968 var curr = start;
3927 try updateFromSlice(Struct, arena, &curr, ".{ .a = 100 }", null, .{});
3969 try updateFromSlice(Struct, gpa, &curr, ".{ .a = 100 }", null, .{});
39283970 try std.testing.expectEqual(end, curr);
39293971
39303972 curr = start;
3931 try updateFromSliceAlloc(Struct, arena, &curr, ".{ .a = 100 }", null, .{});
3973 try updateFromSliceAlloc(Struct, gpa, &curr, ".{ .a = 100 }", null, .{});
39323974 try std.testing.expectEqual(end, curr);
39333975
39343976 curr = start;
......@@ -3936,7 +3978,7 @@ test "std.zon variants" {
39363978 try std.testing.expectEqual(end, curr);
39373979
39383980 curr = start;
3939 try updateFromZoirAlloc(Struct, arena, &curr, ast, zoir, null, .{});
3981 try updateFromZoirAlloc(Struct, gpa, &curr, ast, zoir, null, .{});
39403982 try std.testing.expectEqual(end, curr);
39413983
39423984 curr = start;
......@@ -3944,29 +3986,31 @@ test "std.zon variants" {
39443986 try std.testing.expectEqual(end, curr);
39453987
39463988 curr = start;
3947 try updateFromZoirNodeAlloc(Struct, arena, &curr, ast, zoir, .root, null, .{});
3989 try updateFromZoirNodeAlloc(Struct, gpa, &curr, ast, zoir, .root, null, .{});
39483990 try std.testing.expectEqual(end, curr);
39493991 }
39503992
39513993 // From
39523994 {
3953 const ast = try std.zig.Ast.parse(arena, ".{ .a = 100, .b = 20 }", .{ .mode = .zon });
3954 const zoir = try ZonGen.generate(arena, ast, .{ .parse_str_lits = false });
3995 var ast = try std.zig.Ast.parse(gpa, ".{ .a = 100, .b = 20 }", .{ .mode = .zon });
3996 defer ast.deinit(gpa);
3997 const zoir = try ZonGen.generate(gpa, ast, .{ .parse_str_lits = false });
3998 defer zoir.deinit(gpa);
39553999
39564000 try std.testing.expectEqual(
39574001 end,
3958 try fromSlice(Struct, arena, ".{ .a = 100, .b = 20 }", null, .{}),
4002 try fromSlice(Struct, gpa, ".{ .a = 100, .b = 20 }", null, .{}),
39594003 );
39604004 try std.testing.expectEqual(
39614005 end,
3962 try fromSliceAlloc(Struct, arena, ".{ .a = 100, .b = 20 }", null, .{}),
4006 try fromSliceAlloc(Struct, gpa, ".{ .a = 100, .b = 20 }", null, .{}),
39634007 );
39644008 try std.testing.expectEqual(
39654009 try fromZoir(Struct, ast, zoir, null, .{}),
39664010 end,
39674011 );
39684012 try std.testing.expectEqual(
3969 try fromZoirAlloc(Struct, arena, ast, zoir, null, .{}),
4013 try fromZoirAlloc(Struct, gpa, ast, zoir, null, .{}),
39704014 end,
39714015 );
39724016 try std.testing.expectEqual(
......@@ -3974,7 +4018,7 @@ test "std.zon variants" {
39744018 end,
39754019 );
39764020 try std.testing.expectEqual(
3977 try fromZoirNodeAlloc(Struct, arena, ast, zoir, .root, null, .{}),
4021 try fromZoirNodeAlloc(Struct, gpa, ast, zoir, .root, null, .{}),
39784022 end,
39794023 );
39804024 }