authorgravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-07-19 19:22:07-07:00
committergravatar for mason@gamesbymason.comMason Remaley <mason@gamesbymason.com> 2026-09-26 12:35:38-07:00
loga5bfa7aa453f2a061791501339044db452b93df5
treebcb8d59dc48a242a3d49fe4ccc7e44379207e28d
parented70c643492c7c49057a72ff3e1981cb1ca81e57

Support updating pointers, unions, and optionals


1 files changed, 271 insertions(+), 60 deletions(-)

lib/std/zon/parse.zig+271-60
......@@ -199,10 +199,8 @@ pub fn fromSliceAlloc(T: type, options: FromSliceAllocOptions) error{ OutOfMemor
199199 .arena = options.arena,
200200 .source = options.source,
201201 .diag = options.diag,
202 .parser_options = .{
203 .ignore_unknown_fields = options.ignore_unknown_fields,
204 .ignore_missing_fields = false,
205 },
202 .ignore_unknown_fields = options.ignore_unknown_fields,
203 .initialized = false,
206204 });
207205 return value;
208206}
......@@ -218,10 +216,8 @@ pub fn updateFromSliceAlloc(
218216 .arena = options.arena,
219217 .source = options.source,
220218 .diag = options.diag,
221 .parser_options = .{
222 .ignore_unknown_fields = options.ignore_unknown_fields,
223 .ignore_missing_fields = true,
224 },
219 .ignore_unknown_fields = options.ignore_unknown_fields,
220 .initialized = true,
225221 });
226222}
227223
......@@ -230,7 +226,8 @@ const ParseSliceAllocOptions = struct {
230226 arena: Allocator,
231227 source: [:0]const u8,
232228 diag: ?*Diagnostics,
233 parser_options: Parser.Options,
229 ignore_unknown_fields: bool,
230 initialized: bool,
234231};
235232
236233fn parseSliceAlloc(
......@@ -250,7 +247,8 @@ fn parseSliceAlloc(
250247 .zoir = &zoir,
251248 .node = .root,
252249 .diag = options.diag,
253 .parser_options = options.parser_options,
250 .ignore_unknown_fields = options.ignore_unknown_fields,
251 .initialized = options.initialized,
254252 });
255253}
256254
......@@ -327,10 +325,8 @@ pub fn fromZoirAlloc(T: type, options: FromZoirAllocOptions) error{ OutOfMemory,
327325 .zoir = options.zoir,
328326 .node = options.node,
329327 .diag = options.diag,
330 .parser_options = .{
331 .ignore_unknown_fields = options.ignore_unknown_fields,
332 .ignore_missing_fields = false,
333 },
328 .ignore_unknown_fields = options.ignore_unknown_fields,
329 .initialized = false,
334330 });
335331 return value;
336332}
......@@ -347,10 +343,8 @@ pub fn updateFromZoirAlloc(
347343 .zoir = options.zoir,
348344 .node = options.node,
349345 .diag = options.diag,
350 .parser_options = .{
351 .ignore_unknown_fields = options.ignore_unknown_fields,
352 .ignore_missing_fields = true,
353 },
346 .ignore_unknown_fields = options.ignore_unknown_fields,
347 .initialized = true,
354348 });
355349}
356350
......@@ -360,7 +354,8 @@ const ParseZoirAllocOptions = struct {
360354 zoir: *const Zoir,
361355 node: Zoir.Node.Index,
362356 diag: ?*Diagnostics,
363 parser_options: Parser.Options,
357 ignore_unknown_fields: bool,
358 initialized: bool,
364359};
365360
366361fn parseZoirAlloc(
......@@ -396,11 +391,11 @@ fn parseZoirAlloc(
396391 .arena = options.arena,
397392 .ast = options.ast,
398393 .zoir = options.zoir,
399 .options = options.parser_options,
394 .ignore_unknown_fields = options.ignore_unknown_fields,
400395 .diag = options.diag,
401396 };
402397
403 try parser.parseExprInto(options.node, value);
398 try parser.parseExprInto(options.node, value, options.initialized);
404399}
405400
406401fn requiresAllocator(T: type) bool {
......@@ -429,18 +424,17 @@ const Parser = struct {
429424 ast: *const Ast,
430425 zoir: *const Zoir,
431426 diag: ?*Diagnostics,
432 options: Parser.Options,
433 pointer_depth: usize = 0,
434
435 pub const Options = struct {
436 ignore_unknown_fields: bool,
437 ignore_missing_fields: bool,
438 };
427 ignore_unknown_fields: bool,
439428
440429 const ParseExprError = error{ ParseZon, OutOfMemory };
441430
442 fn parseExprInto(self: *@This(), node: Zoir.Node.Index, out: anytype) ParseExprError!void {
443 return self.parseExprInnerInto(node, out) catch |err| switch (err) {
431 fn parseExprInto(
432 self: *@This(),
433 node: Zoir.Node.Index,
434 out: anytype,
435 initialized: bool,
436 ) ParseExprError!void {
437 return self.parseExprInnerInto(node, out, initialized) catch |err| switch (err) {
444438 error.WrongType => return self.failExpectedType(@TypeOf(out.*), node),
445439 else => |e| return e,
446440 };
......@@ -452,6 +446,7 @@ const Parser = struct {
452446 self: *@This(),
453447 node: Zoir.Node.Index,
454448 out: anytype,
449 initialized: bool,
455450 ) ParseExprInnerError!void {
456451 if (@TypeOf(out.*) == Zoir.Node.Index) {
457452 out.* = node;
......@@ -462,9 +457,9 @@ const Parser = struct {
462457 .optional => |optional| if (node.get(self.zoir) == .null) {
463458 out.* = null;
464459 } else {
465 var new: optional.child = undefined;
466 try self.parseExprInnerInto(node, &new);
467 out.* = new;
460 const child_initialized = initialized and out.* != null;
461 if (!child_initialized) out.* = @as(optional.child, undefined);
462 try self.parseExprInnerInto(node, &out.*.?, child_initialized);
468463 },
469464 .bool => out.* = try self.parseBool(node),
470465 .int => out.* = try self.parseInt(@TypeOf(out.*), node),
......@@ -472,23 +467,31 @@ const Parser = struct {
472467 .@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node),
473468 .pointer => |pointer| switch (pointer.size) {
474469 .one => {
475 const slice = try self.arena.alignedAlloc(
476 pointer.child,
477 if (pointer.attrs.@"align") |a| .fromByteUnits(a) else null,
478 1,
470 // Get a pointer to update
471 const new = b: {
472 // If the existing value is initialized and non const, use it
473 if (initialized and !pointer.attrs.@"const") break :b out.*;
474 // Otherwise, allocate new memory
475 const new = &(try self.arena.alignedAlloc(
476 pointer.child,
477 if (pointer.attrs.@"align") |a| .fromByteUnits(a) else null,
478 1,
479 ))[0];
480 // If the original value was initialized, dupe it into the new memory
481 if (initialized) new.* = out.*.*;
482 break :b new;
483 };
484 try self.parseExprInnerInto(
485 node,
486 new,
487 initialized,
479488 );
480 const new = &slice[0];
481 {
482 self.pointer_depth += 1;
483 defer self.pointer_depth -= 1;
484 try self.parseExprInnerInto(node, new);
485 }
486489 out.* = new;
487490 },
488491 .slice => {
492 // Slices are replaced wholesale since ZON doesn't have syntax for specifying
493 // just a part of a slice.
489494 const new = b: {
490 self.pointer_depth += 1;
491 defer self.pointer_depth -= 1;
492495 const new = try self.parseSlicePointer(@TypeOf(out.*), node);
493496 break :b new;
494497 };
......@@ -501,8 +504,8 @@ const Parser = struct {
501504 .@"struct" => |@"struct"| if (@"struct".is_tuple)
502505 try self.parseTupleInto(node, out)
503506 else
504 try self.parseStructInto(node, out),
505 .@"union" => try self.parseUnionInto(node, out),
507 try self.parseStructInto(node, out, initialized),
508 .@"union" => try self.parseUnionInto(node, out, initialized),
506509
507510 else => comptime unreachable,
508511 }
......@@ -714,7 +717,7 @@ const Parser = struct {
714717
715718 // Parse the elements and return the slice
716719 for (slice, 0..) |*elem, i| {
717 try self.parseExprInto(nodes.at(@intCast(i)), elem);
720 try self.parseExprInto(nodes.at(@intCast(i)), elem, false);
718721 }
719722
720723 return slice;
......@@ -746,7 +749,7 @@ const Parser = struct {
746749
747750 // Parse the elements and return the array
748751 inline for (out, 0..) |*elem, i| {
749 try self.parseExprInto(nodes.at(@intCast(i)), elem);
752 try self.parseExprInto(nodes.at(@intCast(i)), elem, false);
750753 }
751754
752755 if (array.sentinel()) |s| {
......@@ -761,7 +764,12 @@ const Parser = struct {
761764 out.* = array;
762765 }
763766
764 fn parseStructInto(self: *@This(), node: Zoir.Node.Index, out: anytype) !void {
767 fn parseStructInto(
768 self: *@This(),
769 node: Zoir.Node.Index,
770 out: anytype,
771 initialized: bool,
772 ) !void {
765773 const repr = node.get(self.zoir);
766774 const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
767775 .struct_literal => |nodes| nodes,
......@@ -787,7 +795,7 @@ const Parser = struct {
787795 for (0..fields.names.len) |i| {
788796 const name = fields.names[i].get(self.zoir);
789797 const field_index = field_indices.get(name) orelse {
790 if (self.options.ignore_unknown_fields) continue;
798 if (self.ignore_unknown_fields) continue;
791799 return self.failUnexpected(@TypeOf(out.*), "field", node, i, name);
792800 };
793801 if (field_index == comptime_field) {
......@@ -802,10 +810,10 @@ const Parser = struct {
802810 switch (field_index) {
803811 inline 0...(info.field_names.len - 1) => |j| {
804812 if (info.field_attrs[j].@"comptime") unreachable;
805
806813 try self.parseExprInto(
807814 fields.vals.at(@intCast(i)),
808815 &@field(out, info.field_names[j]),
816 initialized,
809817 );
810818 },
811819 else => unreachable, // Can't be out of bounds
......@@ -813,7 +821,7 @@ const Parser = struct {
813821 }
814822
815823 // Fill in any missing default fields
816 if (!self.options.ignore_missing_fields or self.pointer_depth > 0) {
824 if (!initialized) {
817825 inline for (field_found, 0..) |found, i| {
818826 if (!found) {
819827 const field_attrs = info.field_attrs[i];
......@@ -860,20 +868,28 @@ const Parser = struct {
860868 if (info.field_attrs[i].@"comptime") {
861869 return self.failComptimeField(node, i);
862870 } else {
863 try self.parseExprInto(nodes.at(i), &out[i]);
871 try self.parseExprInto(nodes.at(i), &out[i], false);
864872 }
865873 }
866874 }
867875 }
868876
869 fn parseUnionInto(self: *@This(), node: Zoir.Node.Index, out: anytype) !void {
877 fn parseUnionInto(
878 self: *@This(),
879 node: Zoir.Node.Index,
880 out: anytype,
881 initialized: bool,
882 ) !void {
870883 const @"union" = @typeInfo(@TypeOf(out.*)).@"union";
871884
872885 if (@"union".field_names.len == 0) comptime unreachable;
873886
874887 // Gather info on the fields
875888 const field_indices = b: {
876 comptime var kvs_list: [@"union".field_names.len]struct { []const u8, usize } = undefined;
889 comptime var kvs_list: [@"union".field_names.len]struct {
890 []const u8,
891 usize,
892 } = undefined;
877893 inline for (@"union".field_names, 0..) |field_name, i| {
878894 kvs_list[i] = .{ field_name, i };
879895 }
......@@ -893,7 +909,13 @@ const Parser = struct {
893909 const field_index = b: {
894910 const field_name_str = field_name.get(self.zoir);
895911 break :b field_indices.get(field_name_str) orelse
896 return self.failUnexpected(@TypeOf(out.*), "field", node, null, field_name_str);
912 return self.failUnexpected(
913 @TypeOf(out.*),
914 "field",
915 node,
916 null,
917 field_name_str,
918 );
897919 };
898920
899921 // Initialize the union from the given field.
......@@ -926,8 +948,25 @@ const Parser = struct {
926948 if (@"union".field_types[i] == void) {
927949 return self.failNode(field_val, "expected type 'void'");
928950 } else {
929 out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], undefined);
930 try self.parseExprInto(field_val, &@field(out.*, @"union".field_names[i]));
951 const field_initialized = b: {
952 if (!initialized) break :b false;
953 if (@"union".tag_type == null) break :b false;
954 const tag = @field(@"union".tag_type.?, @"union".field_names[i]);
955 if (out.* != tag) break :b false;
956 break :b true;
957 };
958 if (!field_initialized) {
959 out.* = @unionInit(
960 @TypeOf(out.*),
961 @"union".field_names[i],
962 undefined,
963 );
964 }
965 try self.parseExprInto(
966 field_val,
967 &@field(out.*, @"union".field_names[i]),
968 field_initialized,
969 );
931970 }
932971 },
933972 else => unreachable, // Can't be out of bounds
......@@ -4250,7 +4289,7 @@ test "std.zon aligned pointers" {
42504289 try std.testing.expectEqualDeep(expected.inner, found.inner);
42514290}
42524291
4253test "std.zon update" {
4292test "std.zon update basic" {
42544293 const gpa = std.testing.allocator;
42554294 var arena_allocator: ArenaAllocator = .init(gpa);
42564295 defer arena_allocator.deinit();
......@@ -4362,6 +4401,178 @@ test "std.zon update" {
43624401 try std.testing.expectEqualDeep(expected, found);
43634402}
43644403
4404test "std.zon update optionals" {
4405 const gpa = std.testing.allocator;
4406 var arena_allocator: ArenaAllocator = .init(gpa);
4407 defer arena_allocator.deinit();
4408 const arena = arena_allocator.allocator();
4409
4410 const MyStruct = struct {
4411 foo: ?struct { bar: u32 = 1, baz: u32 = 2, qux: u32 },
4412 };
4413
4414 // Updating an optional that starts out null should fill in any default fields we left off
4415 {
4416 var expected: MyStruct = .{ .foo = null };
4417 var found = expected;
4418 try updateFromSlice(MyStruct, &found, .{
4419 .gpa = gpa,
4420 .source = ".{ .foo = .{ .qux = 3 } }",
4421 .diag = null,
4422 });
4423 expected.foo = .{ .qux = 3 };
4424 try std.testing.expectEqual(expected, found);
4425 }
4426
4427 // Updating an optional that starts out null should error if we leave off required fields.
4428 {
4429 var found: MyStruct = .{ .foo = null };
4430 var diag: Diagnostics = .init(arena);
4431 try std.testing.expectError(error.ParseZon, updateFromSlice(MyStruct, &found, .{
4432 .gpa = gpa,
4433 .source = ".{ .foo = .{} }",
4434 .diag = &diag,
4435 }));
4436 try std.testing.expectFmt("1:12: error: missing required field qux\n", "{f}", .{diag});
4437 }
4438
4439 // Updating an optional that starts out non-null should preserve any values we leave off. It's
4440 // also okay to leave off required fields since they were already set.
4441 {
4442 var expected: MyStruct = .{
4443 .foo = .{ .bar = 10, .baz = 20, .qux = 30 },
4444 };
4445 var found = expected;
4446 try updateFromSlice(MyStruct, &found, .{
4447 .gpa = gpa,
4448 .source = ".{ .foo = .{ .baz = 200 } }",
4449 .diag = null,
4450 });
4451 expected.foo.?.baz = 200;
4452 try std.testing.expectEqual(expected, found);
4453 }
4454}
4455
4456test "std.zon update optional pointers" {
4457 const gpa = std.testing.allocator;
4458 var arena_allocator: ArenaAllocator = .init(gpa);
4459 defer arena_allocator.deinit();
4460 const arena = arena_allocator.allocator();
4461
4462 const MyStruct = struct {
4463 foo: ?*const struct { bar: u32 = 1, baz: u32 = 2, qux: u32 },
4464 };
4465
4466 // Just a smoke test since its the combination of behavior we've already tested separately
4467 {
4468 var expected: MyStruct = .{ .foo = null };
4469 var found = expected;
4470 try updateFromSliceAlloc(MyStruct, &found, .{
4471 .gpa = gpa,
4472 .arena = arena,
4473 .source = ".{ .foo = .{ .qux = 3 } }",
4474 .diag = null,
4475 });
4476 expected.foo = &.{ .qux = 3 };
4477 try std.testing.expectEqualDeep(expected, found);
4478 }
4479}
4480
4481test "std.zon update pointers" {
4482 const gpa = std.testing.allocator;
4483 var arena_allocator: ArenaAllocator = .init(gpa);
4484 defer arena_allocator.deinit();
4485 const arena = arena_allocator.allocator();
4486
4487 // Updating a pointer should leave fields we don't specify unchanged
4488 {
4489 const MyStruct = struct {
4490 foo: *struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
4491 };
4492 var ptr: @typeInfo(@FieldType(MyStruct, "foo")).pointer.child = .{ .qux = 3 };
4493 var expected: MyStruct = .{ .foo = &ptr };
4494 var found = expected;
4495 try updateFromSliceAlloc(MyStruct, &found, .{
4496 .gpa = gpa,
4497 .arena = arena,
4498 .source = ".{ .foo = .{ .bar = 100 } }",
4499 .diag = null,
4500 });
4501 expected.foo.bar = 100;
4502 try std.testing.expectEqualDeep(expected, found);
4503 }
4504
4505 // Same thing, but for const pointers
4506 {
4507 const MyStruct = struct {
4508 foo: *const struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
4509 };
4510 var expected: MyStruct = .{ .foo = &.{ .qux = 3 } };
4511 var found = expected;
4512 try updateFromSliceAlloc(MyStruct, &found, .{
4513 .gpa = gpa,
4514 .arena = arena,
4515 .source = ".{ .foo = .{ .bar = 100 } }",
4516 .diag = null,
4517 });
4518 expected.foo = &.{ .bar = 100, .baz = 20, .qux = 3 };
4519 try std.testing.expectEqualDeep(expected, found);
4520 }
4521}
4522
4523test "std.zon update unions" {
4524 const gpa = std.testing.allocator;
4525 var arena_allocator: ArenaAllocator = .init(gpa);
4526 defer arena_allocator.deinit();
4527 const arena = arena_allocator.allocator();
4528
4529 const MyUnion = union(enum) {
4530 none: void,
4531 foo: struct { bar: u32 = 10, baz: u32 = 20, qux: u32 },
4532 };
4533
4534 // Updating a union to a new field should fill in any default sub-fields we left off
4535 {
4536 var expected: MyUnion = .none;
4537 var found = expected;
4538 try updateFromSlice(MyUnion, &found, .{
4539 .gpa = gpa,
4540 .source = ".{ .foo = .{ .qux = 3 } }",
4541 .diag = null,
4542 });
4543 expected = .{ .foo = .{ .qux = 3 } };
4544 try std.testing.expectEqual(expected, found);
4545 }
4546
4547 // Updating a union to a new field should error if we leave off required sub-fields
4548 {
4549 var found: MyUnion = .none;
4550 var diag: Diagnostics = .init(arena);
4551 try std.testing.expectError(error.ParseZon, updateFromSlice(MyUnion, &found, .{
4552 .gpa = gpa,
4553 .source = ".{ .foo = .{} }",
4554 .diag = &diag,
4555 }));
4556 try std.testing.expectFmt("1:12: error: missing required field qux\n", "{f}", .{diag});
4557 }
4558
4559 // Updating a union should preseve any sub-fields that we left off. It's also okay to leave off
4560 // required fields since they were already set.
4561 {
4562 var expected: MyUnion = .{
4563 .foo = .{ .bar = 10, .baz = 20, .qux = 30 },
4564 };
4565 var found = expected;
4566 try updateFromSlice(MyUnion, &found, .{
4567 .gpa = gpa,
4568 .source = ".{ .foo = .{ .baz = 200 } }",
4569 .diag = null,
4570 });
4571 expected.foo.baz = 200;
4572 try std.testing.expectEqual(expected, found);
4573 }
4574}
4575
43654576test "std.zon variants" {
43664577 // Just a smoke test, we don't need to test each variant thoroughly because they all call into
43674578 // the same code.