master
1//! Lower level control over serialization, you can create a new instance with `serializer`.
2//!
3//! Useful when you want control over which fields are serialized, how they're represented,
4//! or want to write a ZON object that does not exist in memory.
5//!
6//! You can serialize values with `value`. To serialize recursive types, the following are provided:
7//! * `valueMaxDepth`
8//! * `valueArbitraryDepth`
9//!
10//! You can also serialize values using specific notations:
11//! * `int`
12//! * `float`
13//! * `codePoint`
14//! * `tuple`
15//! * `tupleMaxDepth`
16//! * `tupleArbitraryDepth`
17//! * `string`
18//! * `multilineString`
19//!
20//! For manual serialization of containers, see:
21//! * `beginStruct`
22//! * `beginTuple`
23
24options: Options = .{},
25indent_level: u8 = 0,
26writer: *Writer,
27
28const Serializer = @This();
29const std = @import("std");
30const assert = std.debug.assert;
31const Writer = std.Io.Writer;
32
33pub const Error = Writer.Error;
34pub const DepthError = Error || error{ExceededMaxDepth};
35
36pub const Options = struct {
37 /// If false, only syntactically necessary whitespace is emitted.
38 whitespace: bool = true,
39};
40
41/// Options for manual serialization of container types.
42pub const ContainerOptions = struct {
43 /// The whitespace style that should be used for this container. Ignored if whitespace is off.
44 whitespace_style: union(enum) {
45 /// If true, wrap every field. If false do not.
46 wrap: bool,
47 /// Automatically decide whether to wrap or not based on the number of fields. Following
48 /// the standard rule of thumb, containers with more than two fields are wrapped.
49 fields: usize,
50 } = .{ .wrap = true },
51
52 fn shouldWrap(self: ContainerOptions) bool {
53 return switch (self.whitespace_style) {
54 .wrap => |wrap| wrap,
55 .fields => |fields| fields > 2,
56 };
57 }
58};
59
60/// Options for serialization of an individual value.
61///
62/// See `SerializeOptions` for more information on these options.
63pub const ValueOptions = struct {
64 emit_codepoint_literals: EmitCodepointLiterals = .never,
65 emit_strings_as_containers: bool = false,
66 emit_default_optional_fields: bool = true,
67};
68
69/// Determines when to emit Unicode code point literals as opposed to integer literals.
70pub const EmitCodepointLiterals = enum {
71 /// Never emit Unicode code point literals.
72 never,
73 /// Emit Unicode code point literals for any `u8` in the printable ASCII range.
74 printable_ascii,
75 /// Emit Unicode code point literals for any unsigned integer with 21 bits or fewer
76 /// whose value is a valid non-surrogate code point.
77 always,
78
79 /// If the value should be emitted as a Unicode codepoint, return it as a u21.
80 fn emitAsCodepoint(self: @This(), val: anytype) ?u21 {
81 // Rule out incompatible integer types
82 switch (@typeInfo(@TypeOf(val))) {
83 .int => |int_info| if (int_info.signedness == .signed or int_info.bits > 21) {
84 return null;
85 },
86 .comptime_int => {},
87 else => comptime unreachable,
88 }
89
90 // Return null if the value shouldn't be printed as a Unicode codepoint, or the value casted
91 // to a u21 if it should.
92 switch (self) {
93 .always => {
94 const c = std.math.cast(u21, val) orelse return null;
95 if (!std.unicode.utf8ValidCodepoint(c)) return null;
96 return c;
97 },
98 .printable_ascii => {
99 const c = std.math.cast(u8, val) orelse return null;
100 if (!std.ascii.isPrint(c)) return null;
101 return c;
102 },
103 .never => {
104 return null;
105 },
106 }
107 }
108};
109
110/// Serialize a value, similar to `serialize`.
111pub fn value(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
112 comptime assert(!typeIsRecursive(@TypeOf(val)));
113 return self.valueArbitraryDepth(val, options);
114}
115
116/// Serialize a value, similar to `serializeMaxDepth`.
117/// Can return `error.ExceededMaxDepth`.
118pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, depth: usize) DepthError!void {
119 try checkValueDepth(val, depth);
120 return self.valueArbitraryDepth(val, options);
121}
122
123/// Serialize a value, similar to `serializeArbitraryDepth`.
124pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
125 comptime assert(canSerializeType(@TypeOf(val)));
126 switch (@typeInfo(@TypeOf(val))) {
127 .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| {
128 self.codePoint(c) catch |err| switch (err) {
129 error.InvalidCodepoint => unreachable, // Already validated
130 else => |e| return e,
131 };
132 } else {
133 try self.int(val);
134 },
135 .float, .comptime_float => try self.float(val),
136 .bool, .null => try self.writer.print("{}", .{val}),
137 .enum_literal => try self.ident(@tagName(val)),
138 .@"enum" => try self.ident(@tagName(val)),
139 .pointer => |pointer| {
140 // Try to serialize as a string
141 const item: ?type = switch (@typeInfo(pointer.child)) {
142 .array => |array| array.child,
143 else => if (pointer.size == .slice) pointer.child else null,
144 };
145 if (item == u8 and
146 (pointer.sentinel() == null or pointer.sentinel() == 0) and
147 !options.emit_strings_as_containers)
148 {
149 return try self.string(val);
150 }
151
152 // Serialize as either a tuple or as the child type
153 switch (pointer.size) {
154 .slice => try self.tupleImpl(val, options),
155 .one => try self.valueArbitraryDepth(val.*, options),
156 else => comptime unreachable,
157 }
158 },
159 .array => {
160 try valueArbitraryDepthArray(self, @TypeOf(val), &val, options);
161 },
162 .vector => |vector| {
163 const array: [vector.len]vector.child = val;
164 try valueArbitraryDepthArray(self, @TypeOf(array), &array, options);
165 },
166 .@"struct" => |@"struct"| if (@"struct".is_tuple) {
167 var container = try self.beginTuple(
168 .{ .whitespace_style = .{ .fields = @"struct".fields.len } },
169 );
170 inline for (val) |field_value| {
171 try container.fieldArbitraryDepth(field_value, options);
172 }
173 try container.end();
174 } else {
175 // Decide which fields to emit
176 const fields, const skipped: [@"struct".fields.len]bool = if (options.emit_default_optional_fields) b: {
177 break :b .{ @"struct".fields.len, @splat(false) };
178 } else b: {
179 var fields = @"struct".fields.len;
180 var skipped: [@"struct".fields.len]bool = @splat(false);
181 inline for (@"struct".fields, &skipped) |field_info, *skip| {
182 if (field_info.default_value_ptr) |ptr| {
183 const default: *const field_info.type = @ptrCast(@alignCast(ptr));
184 const field_value = @field(val, field_info.name);
185 if (std.meta.eql(field_value, default.*)) {
186 skip.* = true;
187 fields -= 1;
188 }
189 }
190 }
191 break :b .{ fields, skipped };
192 };
193
194 // Emit those fields
195 var container = try self.beginStruct(
196 .{ .whitespace_style = .{ .fields = fields } },
197 );
198 inline for (@"struct".fields, skipped) |field_info, skip| {
199 if (!skip) {
200 try container.fieldArbitraryDepth(
201 field_info.name,
202 @field(val, field_info.name),
203 options,
204 );
205 }
206 }
207 try container.end();
208 },
209 .@"union" => |@"union"| {
210 comptime assert(@"union".tag_type != null);
211 switch (val) {
212 inline else => |pl, tag| if (@TypeOf(pl) == void)
213 try self.writer.print(".{s}", .{@tagName(tag)})
214 else {
215 var container = try self.beginStruct(.{ .whitespace_style = .{ .fields = 1 } });
216
217 try container.fieldArbitraryDepth(
218 @tagName(tag),
219 pl,
220 options,
221 );
222
223 try container.end();
224 },
225 }
226 },
227 .optional => if (val) |inner| {
228 try self.valueArbitraryDepth(inner, options);
229 } else {
230 try self.writer.writeAll("null");
231 },
232
233 else => comptime unreachable,
234 }
235}
236
237fn valueArbitraryDepthArray(s: *Serializer, comptime A: type, array: *const A, options: ValueOptions) Error!void {
238 var container = try s.beginTuple(
239 .{ .whitespace_style = .{ .fields = array.len } },
240 );
241 for (array) |elem| {
242 try container.fieldArbitraryDepth(elem, options);
243 }
244 try container.end();
245}
246
247/// Serialize an integer.
248pub fn int(self: *Serializer, val: anytype) Error!void {
249 try self.writer.printInt(val, 10, .lower, .{});
250}
251
252/// Serialize a float.
253pub fn float(self: *Serializer, val: anytype) Error!void {
254 switch (@typeInfo(@TypeOf(val))) {
255 .float => if (std.math.isNan(val)) {
256 return self.writer.writeAll("nan");
257 } else if (std.math.isPositiveInf(val)) {
258 return self.writer.writeAll("inf");
259 } else if (std.math.isNegativeInf(val)) {
260 return self.writer.writeAll("-inf");
261 } else if (std.math.isNegativeZero(val)) {
262 return self.writer.writeAll("-0.0");
263 } else {
264 try self.writer.print("{d}", .{val});
265 },
266 .comptime_float => if (val == 0) {
267 return self.writer.writeAll("0");
268 } else {
269 try self.writer.print("{d}", .{val});
270 },
271 else => comptime unreachable,
272 }
273}
274
275/// Serialize `name` as an identifier prefixed with `.`.
276///
277/// Escapes the identifier if necessary.
278pub fn ident(self: *Serializer, name: []const u8) Error!void {
279 try self.writer.print(".{f}", .{std.zig.fmtIdPU(name)});
280}
281
282pub const CodePointError = Error || error{InvalidCodepoint};
283
284/// Serialize `val` as a Unicode codepoint.
285///
286/// Returns `error.InvalidCodepoint` if `val` is not a valid Unicode codepoint.
287pub fn codePoint(self: *Serializer, val: u21) CodePointError!void {
288 try self.writer.print("'{f}'", .{std.zig.fmtChar(val)});
289}
290
291/// Like `value`, but always serializes `val` as a tuple.
292///
293/// Will fail at comptime if `val` is not a tuple, array, pointer to an array, or slice.
294pub fn tuple(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
295 comptime assert(!typeIsRecursive(@TypeOf(val)));
296 try self.tupleArbitraryDepth(val, options);
297}
298
299/// Like `tuple`, but recursive types are allowed.
300///
301/// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
302pub fn tupleMaxDepth(
303 self: *Serializer,
304 val: anytype,
305 options: ValueOptions,
306 depth: usize,
307) DepthError!void {
308 try checkValueDepth(val, depth);
309 try self.tupleArbitraryDepth(val, options);
310}
311
312/// Like `tuple`, but recursive types are allowed.
313///
314/// It is the caller's responsibility to ensure that `val` does not contain cycles.
315pub fn tupleArbitraryDepth(
316 self: *Serializer,
317 val: anytype,
318 options: ValueOptions,
319) Error!void {
320 try self.tupleImpl(val, options);
321}
322
323fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void {
324 comptime assert(canSerializeType(@TypeOf(val)));
325 switch (@typeInfo(@TypeOf(val))) {
326 .@"struct" => {
327 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
328 inline for (val) |item_val| {
329 try container.fieldArbitraryDepth(item_val, options);
330 }
331 try container.end();
332 },
333 .pointer, .array => {
334 var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } });
335 for (val) |item_val| {
336 try container.fieldArbitraryDepth(item_val, options);
337 }
338 try container.end();
339 },
340 else => comptime unreachable,
341 }
342}
343
344/// Like `value`, but always serializes `val` as a string.
345pub fn string(self: *Serializer, val: []const u8) Error!void {
346 try self.writer.print("\"{f}\"", .{std.zig.fmtString(val)});
347}
348
349/// Options for formatting multiline strings.
350pub const MultilineStringOptions = struct {
351 /// If top level is true, whitespace before and after the multiline string is elided.
352 /// If it is true, a newline is printed, then the value, followed by a newline, and if
353 /// whitespace is true any necessary indentation follows.
354 top_level: bool = false,
355};
356
357pub const MultilineStringError = Error || error{InnerCarriageReturn};
358
359/// Like `value`, but always serializes to a multiline string literal.
360///
361/// Returns `error.InnerCarriageReturn` if `val` contains a CR not followed by a newline,
362/// since multiline strings cannot represent CR without a following newline.
363pub fn multilineString(
364 self: *Serializer,
365 val: []const u8,
366 options: MultilineStringOptions,
367) MultilineStringError!void {
368 // Make sure the string does not contain any carriage returns not followed by a newline
369 var i: usize = 0;
370 while (i < val.len) : (i += 1) {
371 if (val[i] == '\r') {
372 if (i + 1 < val.len) {
373 if (val[i + 1] == '\n') {
374 i += 1;
375 continue;
376 }
377 }
378 return error.InnerCarriageReturn;
379 }
380 }
381
382 if (!options.top_level) {
383 try self.newline();
384 try self.indent();
385 }
386
387 try self.writer.writeAll("\\\\");
388 for (val) |c| {
389 if (c != '\r') {
390 try self.writer.writeByte(c); // We write newlines here even if whitespace off
391 if (c == '\n') {
392 try self.indent();
393 try self.writer.writeAll("\\\\");
394 }
395 }
396 }
397
398 if (!options.top_level) {
399 try self.writer.writeByte('\n'); // Even if whitespace off
400 try self.indent();
401 }
402}
403
404/// Create a `Struct` for writing ZON structs field by field.
405pub fn beginStruct(self: *Serializer, options: ContainerOptions) Error!Struct {
406 return Struct.begin(self, options);
407}
408
409/// Creates a `Tuple` for writing ZON tuples field by field.
410pub fn beginTuple(self: *Serializer, options: ContainerOptions) Error!Tuple {
411 return Tuple.begin(self, options);
412}
413
414fn indent(self: *Serializer) Error!void {
415 if (self.options.whitespace) {
416 try self.writer.splatByteAll(' ', 4 * self.indent_level);
417 }
418}
419
420fn newline(self: *Serializer) Error!void {
421 if (self.options.whitespace) {
422 try self.writer.writeByte('\n');
423 }
424}
425
426fn newlineOrSpace(self: *Serializer, len: usize) Error!void {
427 if (self.containerShouldWrap(len)) {
428 try self.newline();
429 } else {
430 try self.space();
431 }
432}
433
434fn space(self: *Serializer) Error!void {
435 if (self.options.whitespace) {
436 try self.writer.writeByte(' ');
437 }
438}
439
440/// Writes ZON tuples field by field.
441pub const Tuple = struct {
442 container: Container,
443
444 fn begin(parent: *Serializer, options: ContainerOptions) Error!Tuple {
445 return .{
446 .container = try Container.begin(parent, .anon, options),
447 };
448 }
449
450 /// Finishes serializing the tuple.
451 ///
452 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
453 pub fn end(self: *Tuple) Error!void {
454 try self.container.end();
455 self.* = undefined;
456 }
457
458 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
459 pub fn field(
460 self: *Tuple,
461 val: anytype,
462 options: ValueOptions,
463 ) Error!void {
464 try self.container.field(null, val, options);
465 }
466
467 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
468 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
469 pub fn fieldMaxDepth(
470 self: *Tuple,
471 val: anytype,
472 options: ValueOptions,
473 depth: usize,
474 ) DepthError!void {
475 try self.container.fieldMaxDepth(null, val, options, depth);
476 }
477
478 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
479 /// `valueArbitraryDepth`.
480 pub fn fieldArbitraryDepth(
481 self: *Tuple,
482 val: anytype,
483 options: ValueOptions,
484 ) Error!void {
485 try self.container.fieldArbitraryDepth(null, val, options);
486 }
487
488 /// Starts a field with a struct as a value. Returns the struct.
489 pub fn beginStructField(
490 self: *Tuple,
491 options: ContainerOptions,
492 ) Error!Struct {
493 try self.fieldPrefix();
494 return self.container.serializer.beginStruct(options);
495 }
496
497 /// Starts a field with a tuple as a value. Returns the tuple.
498 pub fn beginTupleField(
499 self: *Tuple,
500 options: ContainerOptions,
501 ) Error!Tuple {
502 try self.fieldPrefix();
503 return self.container.serializer.beginTuple(options);
504 }
505
506 /// Print a field prefix. This prints any necessary commas, and whitespace as
507 /// configured. Useful if you want to serialize the field value yourself.
508 pub fn fieldPrefix(self: *Tuple) Error!void {
509 try self.container.fieldPrefix(null);
510 }
511};
512
513/// Writes ZON structs field by field.
514pub const Struct = struct {
515 container: Container,
516
517 fn begin(parent: *Serializer, options: ContainerOptions) Error!Struct {
518 return .{
519 .container = try Container.begin(parent, .named, options),
520 };
521 }
522
523 /// Finishes serializing the struct.
524 ///
525 /// Prints a trailing comma as configured when appropriate, and the closing bracket.
526 pub fn end(self: *Struct) Error!void {
527 try self.container.end();
528 self.* = undefined;
529 }
530
531 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `value`.
532 pub fn field(
533 self: *Struct,
534 name: []const u8,
535 val: anytype,
536 options: ValueOptions,
537 ) Error!void {
538 try self.container.field(name, val, options);
539 }
540
541 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by `valueMaxDepth`.
542 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
543 pub fn fieldMaxDepth(
544 self: *Struct,
545 name: []const u8,
546 val: anytype,
547 options: ValueOptions,
548 depth: usize,
549 ) DepthError!void {
550 try self.container.fieldMaxDepth(name, val, options, depth);
551 }
552
553 /// Serialize a field. Equivalent to calling `fieldPrefix` followed by
554 /// `valueArbitraryDepth`.
555 pub fn fieldArbitraryDepth(
556 self: *Struct,
557 name: []const u8,
558 val: anytype,
559 options: ValueOptions,
560 ) Error!void {
561 try self.container.fieldArbitraryDepth(name, val, options);
562 }
563
564 /// Starts a field with a struct as a value. Returns the struct.
565 pub fn beginStructField(
566 self: *Struct,
567 name: []const u8,
568 options: ContainerOptions,
569 ) Error!Struct {
570 try self.fieldPrefix(name);
571 return self.container.serializer.beginStruct(options);
572 }
573
574 /// Starts a field with a tuple as a value. Returns the tuple.
575 pub fn beginTupleField(
576 self: *Struct,
577 name: []const u8,
578 options: ContainerOptions,
579 ) Error!Tuple {
580 try self.fieldPrefix(name);
581 return self.container.serializer.beginTuple(options);
582 }
583
584 /// Print a field prefix. This prints any necessary commas, the field name (escaped if
585 /// necessary) and whitespace as configured. Useful if you want to serialize the field
586 /// value yourself.
587 pub fn fieldPrefix(self: *Struct, name: []const u8) Error!void {
588 try self.container.fieldPrefix(name);
589 }
590};
591
592const Container = struct {
593 const FieldStyle = enum { named, anon };
594
595 serializer: *Serializer,
596 field_style: FieldStyle,
597 options: ContainerOptions,
598 empty: bool,
599
600 fn begin(
601 sz: *Serializer,
602 field_style: FieldStyle,
603 options: ContainerOptions,
604 ) Error!Container {
605 if (options.shouldWrap()) sz.indent_level +|= 1;
606 try sz.writer.writeAll(".{");
607 return .{
608 .serializer = sz,
609 .field_style = field_style,
610 .options = options,
611 .empty = true,
612 };
613 }
614
615 fn end(self: *Container) Error!void {
616 if (self.options.shouldWrap()) self.serializer.indent_level -|= 1;
617 if (!self.empty) {
618 if (self.options.shouldWrap()) {
619 if (self.serializer.options.whitespace) {
620 try self.serializer.writer.writeByte(',');
621 }
622 try self.serializer.newline();
623 try self.serializer.indent();
624 } else if (!self.shouldElideSpaces()) {
625 try self.serializer.space();
626 }
627 }
628 try self.serializer.writer.writeByte('}');
629 self.* = undefined;
630 }
631
632 fn fieldPrefix(self: *Container, name: ?[]const u8) Error!void {
633 if (!self.empty) {
634 try self.serializer.writer.writeByte(',');
635 }
636 self.empty = false;
637 if (self.options.shouldWrap()) {
638 try self.serializer.newline();
639 } else if (!self.shouldElideSpaces()) {
640 try self.serializer.space();
641 }
642 if (self.options.shouldWrap()) try self.serializer.indent();
643 if (name) |n| {
644 try self.serializer.ident(n);
645 try self.serializer.space();
646 try self.serializer.writer.writeByte('=');
647 try self.serializer.space();
648 }
649 }
650
651 fn field(
652 self: *Container,
653 name: ?[]const u8,
654 val: anytype,
655 options: ValueOptions,
656 ) Error!void {
657 comptime assert(!typeIsRecursive(@TypeOf(val)));
658 try self.fieldArbitraryDepth(name, val, options);
659 }
660
661 /// Returns `error.ExceededMaxDepth` if `depth` is exceeded.
662 fn fieldMaxDepth(
663 self: *Container,
664 name: ?[]const u8,
665 val: anytype,
666 options: ValueOptions,
667 depth: usize,
668 ) DepthError!void {
669 try checkValueDepth(val, depth);
670 try self.fieldArbitraryDepth(name, val, options);
671 }
672
673 fn fieldArbitraryDepth(
674 self: *Container,
675 name: ?[]const u8,
676 val: anytype,
677 options: ValueOptions,
678 ) Error!void {
679 try self.fieldPrefix(name);
680 try self.serializer.valueArbitraryDepth(val, options);
681 }
682
683 fn shouldElideSpaces(self: *const Container) bool {
684 return switch (self.options.whitespace_style) {
685 .fields => |fields| self.field_style != .named and fields == 1,
686 else => false,
687 };
688 }
689};
690
691test Serializer {
692 var discarding: Writer.Discarding = .init(&.{});
693 var s: Serializer = .{ .writer = &discarding.writer };
694 var vec2 = try s.beginStruct(.{});
695 try vec2.field("x", 1.5, .{});
696 try vec2.fieldPrefix("prefix");
697 try s.value(2.5, .{});
698 try vec2.end();
699}
700
701inline fn typeIsRecursive(comptime T: type) bool {
702 return comptime typeIsRecursiveInner(T, &.{});
703}
704
705fn typeIsRecursiveInner(comptime T: type, comptime prev_visited: []const type) bool {
706 for (prev_visited) |V| {
707 if (V == T) return true;
708 }
709 const visited = prev_visited ++ .{T};
710
711 return switch (@typeInfo(T)) {
712 .pointer => |pointer| typeIsRecursiveInner(pointer.child, visited),
713 .optional => |optional| typeIsRecursiveInner(optional.child, visited),
714 .array => |array| typeIsRecursiveInner(array.child, visited),
715 .vector => |vector| typeIsRecursiveInner(vector.child, visited),
716 .@"struct" => |@"struct"| for (@"struct".fields) |field| {
717 if (typeIsRecursiveInner(field.type, visited)) break true;
718 } else false,
719 .@"union" => |@"union"| inline for (@"union".fields) |field| {
720 if (typeIsRecursiveInner(field.type, visited)) break true;
721 } else false,
722 else => false,
723 };
724}
725
726test typeIsRecursive {
727 try std.testing.expect(!typeIsRecursive(bool));
728 try std.testing.expect(!typeIsRecursive(struct { x: i32, y: i32 }));
729 try std.testing.expect(!typeIsRecursive(struct { i32, i32 }));
730 try std.testing.expect(typeIsRecursive(struct { x: i32, y: i32, z: *@This() }));
731 try std.testing.expect(typeIsRecursive(struct {
732 a: struct {
733 const A = @This();
734 b: struct {
735 c: *struct {
736 a: ?A,
737 },
738 },
739 },
740 }));
741 try std.testing.expect(typeIsRecursive(struct {
742 a: [3]*@This(),
743 }));
744 try std.testing.expect(typeIsRecursive(struct {
745 a: union { a: i32, b: *@This() },
746 }));
747}
748
749fn checkValueDepth(val: anytype, depth: usize) error{ExceededMaxDepth}!void {
750 if (depth == 0) return error.ExceededMaxDepth;
751 const child_depth = depth - 1;
752
753 switch (@typeInfo(@TypeOf(val))) {
754 .pointer => |pointer| switch (pointer.size) {
755 .one => try checkValueDepth(val.*, child_depth),
756 .slice => for (val) |item| {
757 try checkValueDepth(item, child_depth);
758 },
759 .c, .many => {},
760 },
761 .array => for (val) |item| {
762 try checkValueDepth(item, child_depth);
763 },
764 .@"struct" => |@"struct"| inline for (@"struct".fields) |field_info| {
765 try checkValueDepth(@field(val, field_info.name), child_depth);
766 },
767 .@"union" => |@"union"| if (@"union".tag_type == null) {
768 return;
769 } else switch (val) {
770 inline else => |payload| {
771 return checkValueDepth(payload, child_depth);
772 },
773 },
774 .optional => if (val) |inner| try checkValueDepth(inner, child_depth),
775 else => {},
776 }
777}
778
779fn expectValueDepthEquals(expected: usize, v: anytype) !void {
780 try checkValueDepth(v, expected);
781 try std.testing.expectError(error.ExceededMaxDepth, checkValueDepth(v, expected - 1));
782}
783
784test checkValueDepth {
785 try expectValueDepthEquals(1, 10);
786 try expectValueDepthEquals(2, .{ .x = 1, .y = 2 });
787 try expectValueDepthEquals(2, .{ 1, 2 });
788 try expectValueDepthEquals(3, .{ 1, .{ 2, 3 } });
789 try expectValueDepthEquals(3, .{ .{ 1, 2 }, 3 });
790 try expectValueDepthEquals(3, .{ .x = 0, .y = 1, .z = .{ .x = 3 } });
791 try expectValueDepthEquals(3, .{ .x = 0, .y = .{ .x = 1 }, .z = 2 });
792 try expectValueDepthEquals(3, .{ .x = .{ .x = 0 }, .y = 1, .z = 2 });
793 try expectValueDepthEquals(2, @as(?u32, 1));
794 try expectValueDepthEquals(1, @as(?u32, null));
795 try expectValueDepthEquals(1, null);
796 try expectValueDepthEquals(2, &1);
797 try expectValueDepthEquals(3, &@as(?u32, 1));
798
799 const Union = union(enum) {
800 x: u32,
801 y: struct { x: u32 },
802 };
803 try expectValueDepthEquals(2, Union{ .x = 1 });
804 try expectValueDepthEquals(3, Union{ .y = .{ .x = 1 } });
805
806 const Recurse = struct { r: ?*const @This() };
807 try expectValueDepthEquals(2, Recurse{ .r = null });
808 try expectValueDepthEquals(5, Recurse{ .r = &Recurse{ .r = null } });
809 try expectValueDepthEquals(8, Recurse{ .r = &Recurse{ .r = &Recurse{ .r = null } } });
810
811 try expectValueDepthEquals(2, @as([]const u8, &.{ 1, 2, 3 }));
812 try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }}));
813}
814
815inline fn canSerializeType(T: type) bool {
816 comptime return canSerializeTypeInner(T, &.{}, false);
817}
818
819fn canSerializeTypeInner(
820 T: type,
821 /// Visited structs and unions, to avoid infinite recursion.
822 /// Tracking more types is unnecessary, and a little complex due to optional nesting.
823 visited: []const type,
824 parent_is_optional: bool,
825) bool {
826 return switch (@typeInfo(T)) {
827 .bool,
828 .int,
829 .float,
830 .comptime_float,
831 .comptime_int,
832 .null,
833 .enum_literal,
834 => true,
835
836 .noreturn,
837 .void,
838 .type,
839 .undefined,
840 .error_union,
841 .error_set,
842 .@"fn",
843 .frame,
844 .@"anyframe",
845 .@"opaque",
846 => false,
847
848 .@"enum" => |@"enum"| @"enum".is_exhaustive,
849
850 .pointer => |pointer| switch (pointer.size) {
851 .one => canSerializeTypeInner(pointer.child, visited, parent_is_optional),
852 .slice => canSerializeTypeInner(pointer.child, visited, false),
853 .many, .c => false,
854 },
855
856 .optional => |optional| if (parent_is_optional)
857 false
858 else
859 canSerializeTypeInner(optional.child, visited, true),
860
861 .array => |array| canSerializeTypeInner(array.child, visited, false),
862 .vector => |vector| canSerializeTypeInner(vector.child, visited, false),
863
864 .@"struct" => |@"struct"| {
865 for (visited) |V| if (T == V) return true;
866 const new_visited = visited ++ .{T};
867 for (@"struct".fields) |field| {
868 if (!canSerializeTypeInner(field.type, new_visited, false)) return false;
869 }
870 return true;
871 },
872 .@"union" => |@"union"| {
873 for (visited) |V| if (T == V) return true;
874 const new_visited = visited ++ .{T};
875 if (@"union".tag_type == null) return false;
876 for (@"union".fields) |field| {
877 if (field.type != void and !canSerializeTypeInner(field.type, new_visited, false)) {
878 return false;
879 }
880 }
881 return true;
882 },
883 };
884}
885
886test canSerializeType {
887 try std.testing.expect(!comptime canSerializeType(void));
888 try std.testing.expect(!comptime canSerializeType(struct { f: [*]u8 }));
889 try std.testing.expect(!comptime canSerializeType(struct { error{foo} }));
890 try std.testing.expect(!comptime canSerializeType(union(enum) { a: void, f: [*c]u8 }));
891 try std.testing.expect(!comptime canSerializeType(@Vector(0, [*c]u8)));
892 try std.testing.expect(!comptime canSerializeType(*?[*c]u8));
893 try std.testing.expect(!comptime canSerializeType(enum(u8) { _ }));
894 try std.testing.expect(!comptime canSerializeType(union { foo: void }));
895 try std.testing.expect(comptime canSerializeType(union(enum) { foo: void }));
896 try std.testing.expect(comptime canSerializeType(comptime_float));
897 try std.testing.expect(comptime canSerializeType(comptime_int));
898 try std.testing.expect(!comptime canSerializeType(struct { comptime foo: ??u8 = null }));
899 try std.testing.expect(comptime canSerializeType(@TypeOf(.foo)));
900 try std.testing.expect(comptime canSerializeType(?u8));
901 try std.testing.expect(comptime canSerializeType(*?*u8));
902 try std.testing.expect(comptime canSerializeType(?struct {
903 foo: ?struct {
904 ?union(enum) {
905 a: ?@Vector(0, ?*u8),
906 },
907 ?struct {
908 f: ?[]?u8,
909 },
910 },
911 }));
912 try std.testing.expect(!comptime canSerializeType(??u8));
913 try std.testing.expect(!comptime canSerializeType(?*?u8));
914 try std.testing.expect(!comptime canSerializeType(*?*?*u8));
915 try std.testing.expect(comptime canSerializeType(struct { x: comptime_int = 2 }));
916 try std.testing.expect(comptime canSerializeType(struct { x: comptime_float = 2 }));
917 try std.testing.expect(comptime canSerializeType(struct { comptime_int }));
918 try std.testing.expect(comptime canSerializeType(struct { comptime x: @TypeOf(.foo) = .foo }));
919 const Recursive = struct { foo: ?*@This() };
920 try std.testing.expect(comptime canSerializeType(Recursive));
921
922 // Make sure we validate nested optional before we early out due to already having seen
923 // a type recursion!
924 try std.testing.expect(!comptime canSerializeType(struct {
925 add_to_visited: ?u8,
926 retrieve_from_visited: ??u8,
927 }));
928}