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}