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...@@ -199,10 +199,8 @@ pub fn fromSliceAlloc(T: type, options: FromSliceAllocOptions) error{ OutOfMemor
199 .arena = options.arena,199 .arena = options.arena,
200 .source = options.source,200 .source = options.source,
201 .diag = options.diag,201 .diag = options.diag,
202 .parser_options = .{202 .ignore_unknown_fields = options.ignore_unknown_fields,
203 .ignore_unknown_fields = options.ignore_unknown_fields,203 .initialized = false,
204 .ignore_missing_fields = false,
205 },
206 });204 });
207 return value;205 return value;
208}206}
...@@ -218,10 +216,8 @@ pub fn updateFromSliceAlloc(...@@ -218,10 +216,8 @@ pub fn updateFromSliceAlloc(
218 .arena = options.arena,216 .arena = options.arena,
219 .source = options.source,217 .source = options.source,
220 .diag = options.diag,218 .diag = options.diag,
221 .parser_options = .{219 .ignore_unknown_fields = options.ignore_unknown_fields,
222 .ignore_unknown_fields = options.ignore_unknown_fields,220 .initialized = true,
223 .ignore_missing_fields = true,
224 },
225 });221 });
226}222}
227223
...@@ -230,7 +226,8 @@ const ParseSliceAllocOptions = struct {...@@ -230,7 +226,8 @@ const ParseSliceAllocOptions = struct {
230 arena: Allocator,226 arena: Allocator,
231 source: [:0]const u8,227 source: [:0]const u8,
232 diag: ?*Diagnostics,228 diag: ?*Diagnostics,
233 parser_options: Parser.Options,229 ignore_unknown_fields: bool,
230 initialized: bool,
234};231};
235232
236fn parseSliceAlloc(233fn parseSliceAlloc(
...@@ -250,7 +247,8 @@ fn parseSliceAlloc(...@@ -250,7 +247,8 @@ fn parseSliceAlloc(
250 .zoir = &zoir,247 .zoir = &zoir,
251 .node = .root,248 .node = .root,
252 .diag = options.diag,249 .diag = options.diag,
253 .parser_options = options.parser_options,250 .ignore_unknown_fields = options.ignore_unknown_fields,
251 .initialized = options.initialized,
254 });252 });
255}253}
256254
...@@ -327,10 +325,8 @@ pub fn fromZoirAlloc(T: type, options: FromZoirAllocOptions) error{ OutOfMemory,...@@ -327,10 +325,8 @@ pub fn fromZoirAlloc(T: type, options: FromZoirAllocOptions) error{ OutOfMemory,
327 .zoir = options.zoir,325 .zoir = options.zoir,
328 .node = options.node,326 .node = options.node,
329 .diag = options.diag,327 .diag = options.diag,
330 .parser_options = .{328 .ignore_unknown_fields = options.ignore_unknown_fields,
331 .ignore_unknown_fields = options.ignore_unknown_fields,329 .initialized = false,
332 .ignore_missing_fields = false,
333 },
334 });330 });
335 return value;331 return value;
336}332}
...@@ -347,10 +343,8 @@ pub fn updateFromZoirAlloc(...@@ -347,10 +343,8 @@ pub fn updateFromZoirAlloc(
347 .zoir = options.zoir,343 .zoir = options.zoir,
348 .node = options.node,344 .node = options.node,
349 .diag = options.diag,345 .diag = options.diag,
350 .parser_options = .{346 .ignore_unknown_fields = options.ignore_unknown_fields,
351 .ignore_unknown_fields = options.ignore_unknown_fields,347 .initialized = true,
352 .ignore_missing_fields = true,
353 },
354 });348 });
355}349}
356350
...@@ -360,7 +354,8 @@ const ParseZoirAllocOptions = struct {...@@ -360,7 +354,8 @@ const ParseZoirAllocOptions = struct {
360 zoir: *const Zoir,354 zoir: *const Zoir,
361 node: Zoir.Node.Index,355 node: Zoir.Node.Index,
362 diag: ?*Diagnostics,356 diag: ?*Diagnostics,
363 parser_options: Parser.Options,357 ignore_unknown_fields: bool,
358 initialized: bool,
364};359};
365360
366fn parseZoirAlloc(361fn parseZoirAlloc(
...@@ -396,11 +391,11 @@ fn parseZoirAlloc(...@@ -396,11 +391,11 @@ fn parseZoirAlloc(
396 .arena = options.arena,391 .arena = options.arena,
397 .ast = options.ast,392 .ast = options.ast,
398 .zoir = options.zoir,393 .zoir = options.zoir,
399 .options = options.parser_options,394 .ignore_unknown_fields = options.ignore_unknown_fields,
400 .diag = options.diag,395 .diag = options.diag,
401 };396 };
402397
403 try parser.parseExprInto(options.node, value);398 try parser.parseExprInto(options.node, value, options.initialized);
404}399}
405400
406fn requiresAllocator(T: type) bool {401fn requiresAllocator(T: type) bool {
...@@ -429,18 +424,17 @@ const Parser = struct {...@@ -429,18 +424,17 @@ const Parser = struct {
429 ast: *const Ast,424 ast: *const Ast,
430 zoir: *const Zoir,425 zoir: *const Zoir,
431 diag: ?*Diagnostics,426 diag: ?*Diagnostics,
432 options: Parser.Options,427 ignore_unknown_fields: bool,
433 pointer_depth: usize = 0,
434
435 pub const Options = struct {
436 ignore_unknown_fields: bool,
437 ignore_missing_fields: bool,
438 };
439428
440 const ParseExprError = error{ ParseZon, OutOfMemory };429 const ParseExprError = error{ ParseZon, OutOfMemory };
441430
442 fn parseExprInto(self: *@This(), node: Zoir.Node.Index, out: anytype) ParseExprError!void {431 fn parseExprInto(
443 return self.parseExprInnerInto(node, out) catch |err| switch (err) {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) {
444 error.WrongType => return self.failExpectedType(@TypeOf(out.*), node),438 error.WrongType => return self.failExpectedType(@TypeOf(out.*), node),
445 else => |e| return e,439 else => |e| return e,
446 };440 };
...@@ -452,6 +446,7 @@ const Parser = struct {...@@ -452,6 +446,7 @@ const Parser = struct {
452 self: *@This(),446 self: *@This(),
453 node: Zoir.Node.Index,447 node: Zoir.Node.Index,
454 out: anytype,448 out: anytype,
449 initialized: bool,
455 ) ParseExprInnerError!void {450 ) ParseExprInnerError!void {
456 if (@TypeOf(out.*) == Zoir.Node.Index) {451 if (@TypeOf(out.*) == Zoir.Node.Index) {
457 out.* = node;452 out.* = node;
...@@ -462,9 +457,9 @@ const Parser = struct {...@@ -462,9 +457,9 @@ const Parser = struct {
462 .optional => |optional| if (node.get(self.zoir) == .null) {457 .optional => |optional| if (node.get(self.zoir) == .null) {
463 out.* = null;458 out.* = null;
464 } else {459 } else {
465 var new: optional.child = undefined;460 const child_initialized = initialized and out.* != null;
466 try self.parseExprInnerInto(node, &new);461 if (!child_initialized) out.* = @as(optional.child, undefined);
467 out.* = new;462 try self.parseExprInnerInto(node, &out.*.?, child_initialized);
468 },463 },
469 .bool => out.* = try self.parseBool(node),464 .bool => out.* = try self.parseBool(node),
470 .int => out.* = try self.parseInt(@TypeOf(out.*), node),465 .int => out.* = try self.parseInt(@TypeOf(out.*), node),
...@@ -472,23 +467,31 @@ const Parser = struct {...@@ -472,23 +467,31 @@ const Parser = struct {
472 .@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node),467 .@"enum" => out.* = try self.parseEnumLiteral(@TypeOf(out.*), node),
473 .pointer => |pointer| switch (pointer.size) {468 .pointer => |pointer| switch (pointer.size) {
474 .one => {469 .one => {
475 const slice = try self.arena.alignedAlloc(470 // Get a pointer to update
476 pointer.child,471 const new = b: {
477 if (pointer.attrs.@"align") |a| .fromByteUnits(a) else null,472 // If the existing value is initialized and non const, use it
478 1,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,
479 );488 );
480 const new = &slice[0];
481 {
482 self.pointer_depth += 1;
483 defer self.pointer_depth -= 1;
484 try self.parseExprInnerInto(node, new);
485 }
486 out.* = new;489 out.* = new;
487 },490 },
488 .slice => {491 .slice => {
492 // Slices are replaced wholesale since ZON doesn't have syntax for specifying
493 // just a part of a slice.
489 const new = b: {494 const new = b: {
490 self.pointer_depth += 1;
491 defer self.pointer_depth -= 1;
492 const new = try self.parseSlicePointer(@TypeOf(out.*), node);495 const new = try self.parseSlicePointer(@TypeOf(out.*), node);
493 break :b new;496 break :b new;
494 };497 };
...@@ -501,8 +504,8 @@ const Parser = struct {...@@ -501,8 +504,8 @@ const Parser = struct {
501 .@"struct" => |@"struct"| if (@"struct".is_tuple)504 .@"struct" => |@"struct"| if (@"struct".is_tuple)
502 try self.parseTupleInto(node, out)505 try self.parseTupleInto(node, out)
503 else506 else
504 try self.parseStructInto(node, out),507 try self.parseStructInto(node, out, initialized),
505 .@"union" => try self.parseUnionInto(node, out),508 .@"union" => try self.parseUnionInto(node, out, initialized),
506509
507 else => comptime unreachable,510 else => comptime unreachable,
508 }511 }
...@@ -714,7 +717,7 @@ const Parser = struct {...@@ -714,7 +717,7 @@ const Parser = struct {
714717
715 // Parse the elements and return the slice718 // Parse the elements and return the slice
716 for (slice, 0..) |*elem, i| {719 for (slice, 0..) |*elem, i| {
717 try self.parseExprInto(nodes.at(@intCast(i)), elem);720 try self.parseExprInto(nodes.at(@intCast(i)), elem, false);
718 }721 }
719722
720 return slice;723 return slice;
...@@ -746,7 +749,7 @@ const Parser = struct {...@@ -746,7 +749,7 @@ const Parser = struct {
746749
747 // Parse the elements and return the array750 // Parse the elements and return the array
748 inline for (out, 0..) |*elem, i| {751 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);
750 }753 }
751754
752 if (array.sentinel()) |s| {755 if (array.sentinel()) |s| {
...@@ -761,7 +764,12 @@ const Parser = struct {...@@ -761,7 +764,12 @@ const Parser = struct {
761 out.* = array;764 out.* = array;
762 }765 }
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 {
765 const repr = node.get(self.zoir);773 const repr = node.get(self.zoir);
766 const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {774 const fields: @FieldType(Zoir.Node, "struct_literal") = switch (repr) {
767 .struct_literal => |nodes| nodes,775 .struct_literal => |nodes| nodes,
...@@ -787,7 +795,7 @@ const Parser = struct {...@@ -787,7 +795,7 @@ const Parser = struct {
787 for (0..fields.names.len) |i| {795 for (0..fields.names.len) |i| {
788 const name = fields.names[i].get(self.zoir);796 const name = fields.names[i].get(self.zoir);
789 const field_index = field_indices.get(name) orelse {797 const field_index = field_indices.get(name) orelse {
790 if (self.options.ignore_unknown_fields) continue;798 if (self.ignore_unknown_fields) continue;
791 return self.failUnexpected(@TypeOf(out.*), "field", node, i, name);799 return self.failUnexpected(@TypeOf(out.*), "field", node, i, name);
792 };800 };
793 if (field_index == comptime_field) {801 if (field_index == comptime_field) {
...@@ -802,10 +810,10 @@ const Parser = struct {...@@ -802,10 +810,10 @@ const Parser = struct {
802 switch (field_index) {810 switch (field_index) {
803 inline 0...(info.field_names.len - 1) => |j| {811 inline 0...(info.field_names.len - 1) => |j| {
804 if (info.field_attrs[j].@"comptime") unreachable;812 if (info.field_attrs[j].@"comptime") unreachable;
805
806 try self.parseExprInto(813 try self.parseExprInto(
807 fields.vals.at(@intCast(i)),814 fields.vals.at(@intCast(i)),
808 &@field(out, info.field_names[j]),815 &@field(out, info.field_names[j]),
816 initialized,
809 );817 );
810 },818 },
811 else => unreachable, // Can't be out of bounds819 else => unreachable, // Can't be out of bounds
...@@ -813,7 +821,7 @@ const Parser = struct {...@@ -813,7 +821,7 @@ const Parser = struct {
813 }821 }
814822
815 // Fill in any missing default fields823 // Fill in any missing default fields
816 if (!self.options.ignore_missing_fields or self.pointer_depth > 0) {824 if (!initialized) {
817 inline for (field_found, 0..) |found, i| {825 inline for (field_found, 0..) |found, i| {
818 if (!found) {826 if (!found) {
819 const field_attrs = info.field_attrs[i];827 const field_attrs = info.field_attrs[i];
...@@ -860,20 +868,28 @@ const Parser = struct {...@@ -860,20 +868,28 @@ const Parser = struct {
860 if (info.field_attrs[i].@"comptime") {868 if (info.field_attrs[i].@"comptime") {
861 return self.failComptimeField(node, i);869 return self.failComptimeField(node, i);
862 } else {870 } else {
863 try self.parseExprInto(nodes.at(i), &out[i]);871 try self.parseExprInto(nodes.at(i), &out[i], false);
864 }872 }
865 }873 }
866 }874 }
867 }875 }
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 {
870 const @"union" = @typeInfo(@TypeOf(out.*)).@"union";883 const @"union" = @typeInfo(@TypeOf(out.*)).@"union";
871884
872 if (@"union".field_names.len == 0) comptime unreachable;885 if (@"union".field_names.len == 0) comptime unreachable;
873886
874 // Gather info on the fields887 // Gather info on the fields
875 const field_indices = b: {888 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;
877 inline for (@"union".field_names, 0..) |field_name, i| {893 inline for (@"union".field_names, 0..) |field_name, i| {
878 kvs_list[i] = .{ field_name, i };894 kvs_list[i] = .{ field_name, i };
879 }895 }
...@@ -893,7 +909,13 @@ const Parser = struct {...@@ -893,7 +909,13 @@ const Parser = struct {
893 const field_index = b: {909 const field_index = b: {
894 const field_name_str = field_name.get(self.zoir);910 const field_name_str = field_name.get(self.zoir);
895 break :b field_indices.get(field_name_str) orelse911 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 );
897 };919 };
898920
899 // Initialize the union from the given field.921 // Initialize the union from the given field.
...@@ -926,8 +948,25 @@ const Parser = struct {...@@ -926,8 +948,25 @@ const Parser = struct {
926 if (@"union".field_types[i] == void) {948 if (@"union".field_types[i] == void) {
927 return self.failNode(field_val, "expected type 'void'");949 return self.failNode(field_val, "expected type 'void'");
928 } else {950 } else {
929 out.* = @unionInit(@TypeOf(out.*), @"union".field_names[i], undefined);951 const field_initialized = b: {
930 try self.parseExprInto(field_val, &@field(out.*, @"union".field_names[i]));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 );
931 }970 }
932 },971 },
933 else => unreachable, // Can't be out of bounds972 else => unreachable, // Can't be out of bounds
...@@ -4250,7 +4289,7 @@ test "std.zon aligned pointers" {...@@ -4250,7 +4289,7 @@ test "std.zon aligned pointers" {
4250 try std.testing.expectEqualDeep(expected.inner, found.inner);4289 try std.testing.expectEqualDeep(expected.inner, found.inner);
4251}4290}
42524291
4253test "std.zon update" {4292test "std.zon update basic" {
4254 const gpa = std.testing.allocator;4293 const gpa = std.testing.allocator;
4255 var arena_allocator: ArenaAllocator = .init(gpa);4294 var arena_allocator: ArenaAllocator = .init(gpa);
4256 defer arena_allocator.deinit();4295 defer arena_allocator.deinit();
...@@ -4362,6 +4401,178 @@ test "std.zon update" {...@@ -4362,6 +4401,178 @@ test "std.zon update" {
4362 try std.testing.expectEqualDeep(expected, found);4401 try std.testing.expectEqualDeep(expected, found);
4363}4402}
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
4365test "std.zon variants" {4576test "std.zon variants" {
4366 // Just a smoke test, we don't need to test each variant thoroughly because they all call into4577 // Just a smoke test, we don't need to test each variant thoroughly because they all call into
4367 // the same code.4578 // the same code.