Commit d5d0942a0d

Marc Tiehuis <marc@tiehu.is>
2019-06-17 12:03:56
Small cleanup of fmt.zig
Use inferred enum literals and split the large test case up.
1 parent 50c8a93
Changed files (1)
std/fmt.zig
@@ -13,7 +13,13 @@ pub const default_max_depth = 3;
 /// Renders fmt string with args, calling output with slices of bytes.
 /// If `output` returns an error, the error is returned from `format` and
 /// `output` is not called again.
-pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, comptime fmt: []const u8, args: ...) Errors!void {
+pub fn format(
+    context: var,
+    comptime Errors: type,
+    output: fn (@typeOf(context), []const u8) Errors!void,
+    comptime fmt: []const u8,
+    args: ...,
+) Errors!void {
     const State = enum {
         Start,
         OpenBrace,
@@ -28,7 +34,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
 
     inline for (fmt) |c, i| {
         switch (state) {
-            State.Start => switch (c) {
+            .Start => switch (c) {
                 '{' => {
                     if (start_index < i) {
                         try output(context, fmt[start_index..i]);
@@ -45,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
                 },
                 else => {},
             },
-            State.OpenBrace => switch (c) {
+            .OpenBrace => switch (c) {
                 '{' => {
                     state = State.Start;
                     start_index = i;
@@ -61,14 +67,14 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
                     state = State.FormatString;
                 },
             },
-            State.CloseBrace => switch (c) {
+            .CloseBrace => switch (c) {
                 '}' => {
                     state = State.Start;
                     start_index = i;
                 },
                 else => @compileError("Single '}' encountered in format string"),
             },
-            State.FormatString => switch (c) {
+            .FormatString => switch (c) {
                 '}' => {
                     const s = start_index + 1;
                     try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth);
@@ -78,7 +84,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
                 },
                 else => {},
             },
-            State.Pointer => switch (c) {
+            .Pointer => switch (c) {
                 '}' => {
                     try output(context, @typeName(@typeOf(args[next_arg]).Child));
                     try output(context, "@");
@@ -114,88 +120,93 @@ pub fn formatType(
 ) Errors!void {
     const T = @typeOf(value);
     switch (@typeInfo(T)) {
-        builtin.TypeId.ComptimeInt, builtin.TypeId.Int, builtin.TypeId.Float => {
+        .ComptimeInt, .Int, .Float => {
             return formatValue(value, fmt, context, Errors, output);
         },
-        builtin.TypeId.Void => {
+        .Void => {
             return output(context, "void");
         },
-        builtin.TypeId.Bool => {
+        .Bool => {
             return output(context, if (value) "true" else "false");
         },
-        builtin.TypeId.Optional => {
+        .Optional => {
             if (value) |payload| {
                 return formatType(payload, fmt, context, Errors, output, max_depth);
             } else {
                 return output(context, "null");
             }
         },
-        builtin.TypeId.ErrorUnion => {
+        .ErrorUnion => {
             if (value) |payload| {
                 return formatType(payload, fmt, context, Errors, output, max_depth);
             } else |err| {
                 return formatType(err, fmt, context, Errors, output, max_depth);
             }
         },
-        builtin.TypeId.ErrorSet => {
+        .ErrorSet => {
             try output(context, "error.");
             return output(context, @errorName(value));
         },
-        builtin.TypeId.Promise => {
+        .Promise => {
             return format(context, Errors, output, "promise@{x}", @ptrToInt(value));
         },
-        builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => {
-            if (comptime std.meta.trait.hasFn("format")(T)) return value.format(fmt, context, Errors, output);
+        .Enum => {
+            if (comptime std.meta.trait.hasFn("format")(T)) {
+                return value.format(fmt, context, Errors, output);
+            }
 
             try output(context, @typeName(T));
-            switch (comptime @typeId(T)) {
-                builtin.TypeId.Enum => {
-                    try output(context, ".");
-                    try formatType(@tagName(value), "", context, Errors, output, max_depth);
-                    return;
-                },
-                builtin.TypeId.Struct => {
-                    if (max_depth == 0) {
-                        return output(context, "{ ... }");
-                    }
-                    comptime var field_i = 0;
-                    inline while (field_i < @memberCount(T)) : (field_i += 1) {
-                        if (field_i == 0) {
-                            try output(context, "{ .");
-                        } else {
-                            try output(context, ", .");
-                        }
-                        try output(context, @memberName(T, field_i));
-                        try output(context, " = ");
-                        try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1);
-                    }
-                    try output(context, " }");
-                },
-                builtin.TypeId.Union => {
-                    if (max_depth == 0) {
-                        return output(context, "{ ... }");
-                    }
-                    const info = @typeInfo(T).Union;
-                    if (info.tag_type) |UnionTagType| {
-                        try output(context, "{ .");
-                        try output(context, @tagName(UnionTagType(value)));
-                        try output(context, " = ");
-                        inline for (info.fields) |u_field| {
-                            if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {
-                                try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1);
-                            }
-                        }
-                        try output(context, " }");
-                    } else {
-                        try format(context, Errors, output, "@{x}", @ptrToInt(&value));
+            try output(context, ".");
+            return formatType(@tagName(value), "", context, Errors, output, max_depth);
+        },
+        .Union => {
+            if (comptime std.meta.trait.hasFn("format")(T)) {
+                return value.format(fmt, context, Errors, output);
+            }
+
+            try output(context, @typeName(T));
+            if (max_depth == 0) {
+                return output(context, "{ ... }");
+            }
+            const info = @typeInfo(T).Union;
+            if (info.tag_type) |UnionTagType| {
+                try output(context, "{ .");
+                try output(context, @tagName(UnionTagType(value)));
+                try output(context, " = ");
+                inline for (info.fields) |u_field| {
+                    if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) {
+                        try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1);
                     }
-                },
-                else => unreachable,
+                }
+                try output(context, " }");
+            } else {
+                try format(context, Errors, output, "@{x}", @ptrToInt(&value));
             }
-            return;
         },
-        builtin.TypeId.Pointer => |ptr_info| switch (ptr_info.size) {
-            builtin.TypeInfo.Pointer.Size.One => switch (@typeInfo(ptr_info.child)) {
+        .Struct => {
+            if (comptime std.meta.trait.hasFn("format")(T)) {
+                return value.format(fmt, context, Errors, output);
+            }
+
+            try output(context, @typeName(T));
+            if (max_depth == 0) {
+                return output(context, "{ ... }");
+            }
+            comptime var field_i = 0;
+            inline while (field_i < @memberCount(T)) : (field_i += 1) {
+                if (field_i == 0) {
+                    try output(context, "{ .");
+                } else {
+                    try output(context, ", .");
+                }
+                try output(context, @memberName(T, field_i));
+                try output(context, " = ");
+                try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1);
+            }
+            try output(context, " }");
+        },
+        .Pointer => |ptr_info| switch (ptr_info.size) {
+            .One => switch (@typeInfo(ptr_info.child)) {
                 builtin.TypeId.Array => |info| {
                     if (info.child == u8) {
                         return formatText(value, fmt, context, Errors, output);
@@ -207,7 +218,7 @@ pub fn formatType(
                 },
                 else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)),
             },
-            builtin.TypeInfo.Pointer.Size.Many => {
+            .Many => {
                 if (ptr_info.child == u8) {
                     if (fmt.len > 0 and fmt[0] == 's') {
                         const len = mem.len(u8, value);
@@ -216,7 +227,7 @@ pub fn formatType(
                 }
                 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
             },
-            builtin.TypeInfo.Pointer.Size.Slice => {
+            .Slice => {
                 if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) {
                     return formatText(value, fmt, context, Errors, output);
                 }
@@ -225,17 +236,17 @@ pub fn formatType(
                 }
                 return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr));
             },
-            builtin.TypeInfo.Pointer.Size.C => {
+            .C => {
                 return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value));
             },
         },
-        builtin.TypeId.Array => |info| {
+        .Array => |info| {
             if (info.child == u8) {
                 return formatText(value, fmt, context, Errors, output);
             }
             return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value));
         },
-        builtin.TypeId.Fn => {
+        .Fn => {
             return format(context, Errors, output, "{}@{x}", @typeName(T), @ptrToInt(value));
         },
         else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"),
@@ -249,24 +260,24 @@ fn formatValue(
     comptime Errors: type,
     output: fn (@typeOf(context), []const u8) Errors!void,
 ) Errors!void {
-    if (fmt.len > 0) {
-        if (fmt[0] == 'B') {
-            comptime var width: ?usize = null;
-            if (fmt.len > 1) {
-                if (fmt[1] == 'i') {
-                    if (fmt.len > 2) width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable);
-                    return formatBytes(value, width, 1024, context, Errors, output);
+    if (fmt.len > 0 and fmt[0] == 'B') {
+        comptime var width: ?usize = null;
+        if (fmt.len > 1) {
+            if (fmt[1] == 'i') {
+                if (fmt.len > 2) {
+                    width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable);
                 }
-                width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
+                return formatBytes(value, width, 1024, context, Errors, output);
             }
-            return formatBytes(value, width, 1000, context, Errors, output);
+            width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable);
         }
+        return formatBytes(value, width, 1000, context, Errors, output);
     }
 
     const T = @typeOf(value);
     switch (@typeId(T)) {
-        builtin.TypeId.Float => return formatFloatValue(value, fmt, context, Errors, output),
-        builtin.TypeId.Int, builtin.TypeId.ComptimeInt => return formatIntValue(value, fmt, context, Errors, output),
+        .Float => return formatFloatValue(value, fmt, context, Errors, output),
+        .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output),
         else => comptime unreachable,
     }
 }
@@ -797,7 +808,7 @@ pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T {
     }
 }
 
-test "fmt.parseInt" {
+test "parseInt" {
     testing.expect((parseInt(i32, "-10", 10) catch unreachable) == -10);
     testing.expect((parseInt(i32, "+10", 10) catch unreachable) == 10);
     testing.expect(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter);
@@ -828,7 +839,7 @@ pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsigned
     return x;
 }
 
-test "fmt.parseUnsigned" {
+test "parseUnsigned" {
     testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124);
     testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535);
     testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10));
@@ -913,7 +924,7 @@ fn countSize(size: *usize, bytes: []const u8) (error{}!void) {
     size.* += bytes.len;
 }
 
-test "buf print int" {
+test "bufPrintInt" {
     var buffer: [100]u8 = undefined;
     const buf = buffer[0..];
     testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110"));
@@ -949,7 +960,7 @@ test "parse unsigned comptime" {
     }
 }
 
-test "fmt.format" {
+test "fmt.optional" {
     {
         const value: ?i32 = 1234;
         try testFmt("optional: 1234\n", "optional: {}\n", value);
@@ -958,6 +969,9 @@ test "fmt.format" {
         const value: ?i32 = null;
         try testFmt("optional: null\n", "optional: {}\n", value);
     }
+}
+
+test "fmt.error" {
     {
         const value: anyerror!i32 = 1234;
         try testFmt("error union: 1234\n", "error union: {}\n", value);
@@ -966,10 +980,16 @@ test "fmt.format" {
         const value: anyerror!i32 = error.InvalidChar;
         try testFmt("error union: error.InvalidChar\n", "error union: {}\n", value);
     }
+}
+
+test "fmt.int.small" {
     {
         const value: u3 = 0b101;
         try testFmt("u3: 5\n", "u3: {}\n", value);
     }
+}
+
+test "fmt.int.specifier" {
     {
         const value: u8 = 'a';
         try testFmt("u8: a\n", "u8: {c}\n", value);
@@ -978,6 +998,9 @@ test "fmt.format" {
         const value: u8 = 0b1100;
         try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", value);
     }
+}
+
+test "fmt.buffer" {
     {
         var buf1: [32]u8 = undefined;
         var context = BufPrintContext{ .remaining = buf1[0..] };
@@ -995,6 +1018,9 @@ test "fmt.format" {
         res = buf1[0 .. buf1.len - context.remaining.len];
         testing.expect(mem.eql(u8, res, "1100"));
     }
+}
+
+test "fmt.array" {
     {
         const value: [3]u8 = "abc";
         try testFmt("array: abc\n", "array: {}\n", value);
@@ -1007,6 +1033,9 @@ test "fmt.format" {
             &value,
         );
     }
+}
+
+test "fmt.slice" {
     {
         const value: []const u8 = "abc";
         try testFmt("slice: abc\n", "slice: {}\n", value);
@@ -1015,6 +1044,12 @@ test "fmt.format" {
         const value = @intToPtr([*]const []const u8, 0xdeadbeef)[0..0];
         try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value);
     }
+
+    try testFmt("buf: Test \n", "buf: {s5}\n", "Test");
+    try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test");
+}
+
+test "fmt.pointer" {
     {
         const value = @intToPtr(*i32, 0xdeadbeef);
         try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value);
@@ -1028,12 +1063,19 @@ test "fmt.format" {
         const value = @intToPtr(fn () void, 0xdeadbeef);
         try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", value);
     }
-    try testFmt("buf: Test \n", "buf: {s5}\n", "Test");
-    try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test");
+}
+
+test "fmt.cstr" {
     try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C");
     try testFmt("cstr: Test C    \n", "cstr: {s10}\n", c"Test C");
+}
+
+test "fmt.filesize" {
     try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024));
     try testFmt("file size: 66.06MB\n", "file size: {B2}\n", usize(63 * 1024 * 1024));
+}
+
+test "fmt.struct" {
     {
         const Struct = struct {
             field: u8,
@@ -1050,15 +1092,19 @@ test "fmt.format" {
         const value = Struct{ .a = 0, .b = 1 };
         try testFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", value);
     }
-    {
-        const Enum = enum {
-            One,
-            Two,
-        };
-        const value = Enum.Two;
-        try testFmt("enum: Enum.Two\n", "enum: {}\n", value);
-        try testFmt("enum: Enum.Two\n", "enum: {}\n", &value);
-    }
+}
+
+test "fmt.enum" {
+    const Enum = enum {
+        One,
+        Two,
+    };
+    const value = Enum.Two;
+    try testFmt("enum: Enum.Two\n", "enum: {}\n", value);
+    try testFmt("enum: Enum.Two\n", "enum: {}\n", &value);
+}
+
+test "fmt.float.scientific" {
     {
         var buf1: [32]u8 = undefined;
         const value: f32 = 1.34;
@@ -1088,6 +1134,9 @@ test "fmt.format" {
             testing.expect(mem.eql(u8, result, "f64: 9.99996e-40\n"));
         }
     }
+}
+
+test "fmt.float.scientific.precision" {
     {
         var buf1: [32]u8 = undefined;
         const value: f64 = 1.409706e-42;
@@ -1114,6 +1163,9 @@ test "fmt.format" {
         const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
         testing.expect(mem.eql(u8, result, "f64: 1.00001e+05\n"));
     }
+}
+
+test "fmt.float.special" {
     {
         var buf1: [32]u8 = undefined;
         const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
@@ -1136,6 +1188,9 @@ test "fmt.format" {
         const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
         testing.expect(mem.eql(u8, result, "f64: -inf\n"));
     }
+}
+
+test "fmt.float.decimal" {
     {
         var buf1: [64]u8 = undefined;
         const value: f64 = 1.52314e+29;
@@ -1216,7 +1271,9 @@ test "fmt.format" {
         const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
         testing.expect(mem.eql(u8, result, "f64: 0.00000\n"));
     }
-    // libc checks
+}
+
+test "fmt.float.libc.sanity" {
     {
         var buf1: [32]u8 = undefined;
         const value: f64 = f64(@bitCast(f32, u32(916964781)));
@@ -1267,127 +1324,127 @@ test "fmt.format" {
         const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
         testing.expect(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
     }
-    //custom type format
-    {
-        const Vec2 = struct {
-            const SelfType = @This();
-            x: f32,
-            y: f32,
-
-            pub fn format(
-                self: SelfType,
-                comptime fmt: []const u8,
-                context: var,
-                comptime Errors: type,
-                output: fn (@typeOf(context), []const u8) Errors!void,
-            ) Errors!void {
-                switch (fmt.len) {
-                    0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),
-                    1 => switch (fmt[0]) {
-                        //point format
-                        'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),
-                        //dimension format
-                        'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y),
-                        else => unreachable,
-                    },
+}
+
+test "fmt.custom" {
+    const Vec2 = struct {
+        const SelfType = @This();
+        x: f32,
+        y: f32,
+
+        pub fn format(
+            self: SelfType,
+            comptime fmt: []const u8,
+            context: var,
+            comptime Errors: type,
+            output: fn (@typeOf(context), []const u8) Errors!void,
+        ) Errors!void {
+            switch (fmt.len) {
+                0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),
+                1 => switch (fmt[0]) {
+                    //point format
+                    'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y),
+                    //dimension format
+                    'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y),
                     else => unreachable,
-                }
+                },
+                else => unreachable,
             }
-        };
+        }
+    };
 
-        var buf1: [32]u8 = undefined;
-        var value = Vec2{
-            .x = 10.2,
-            .y = 2.22,
-        };
-        try testFmt("point: (10.200,2.220)\n", "point: {}\n", &value);
-        try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", &value);
+    var buf1: [32]u8 = undefined;
+    var value = Vec2{
+        .x = 10.2,
+        .y = 2.22,
+    };
+    try testFmt("point: (10.200,2.220)\n", "point: {}\n", &value);
+    try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", &value);
 
-        // same thing but not passing a pointer
-        try testFmt("point: (10.200,2.220)\n", "point: {}\n", value);
-        try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value);
-    }
-    //struct format
-    {
-        const S = struct {
-            a: u32,
-            b: anyerror,
-        };
+    // same thing but not passing a pointer
+    try testFmt("point: (10.200,2.220)\n", "point: {}\n", value);
+    try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value);
+}
 
-        const inst = S{
-            .a = 456,
-            .b = error.Unused,
-        };
+test "fmt.struct" {
+    const S = struct {
+        a: u32,
+        b: anyerror,
+    };
 
-        try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst);
-    }
-    //union format
-    {
-        const TU = union(enum) {
-            float: f32,
-            int: u32,
-        };
+    const inst = S{
+        .a = 456,
+        .b = error.Unused,
+    };
 
-        const UU = union {
-            float: f32,
-            int: u32,
-        };
+    try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst);
+}
 
-        const EU = extern union {
-            float: f32,
-            int: u32,
-        };
+test "fmt.union" {
+    const TU = union(enum) {
+        float: f32,
+        int: u32,
+    };
 
-        const tu_inst = TU{ .int = 123 };
-        const uu_inst = UU{ .int = 456 };
-        const eu_inst = EU{ .float = 321.123 };
+    const UU = union {
+        float: f32,
+        int: u32,
+    };
 
-        try testFmt("TU{ .int = 123 }", "{}", tu_inst);
+    const EU = extern union {
+        float: f32,
+        int: u32,
+    };
 
-        var buf: [100]u8 = undefined;
-        const uu_result = try bufPrint(buf[0..], "{}", uu_inst);
-        testing.expect(mem.eql(u8, uu_result[0..3], "UU@"));
+    const tu_inst = TU{ .int = 123 };
+    const uu_inst = UU{ .int = 456 };
+    const eu_inst = EU{ .float = 321.123 };
 
-        const eu_result = try bufPrint(buf[0..], "{}", eu_inst);
-        testing.expect(mem.eql(u8, uu_result[0..3], "EU@"));
-    }
-    //enum format
-    {
-        const E = enum {
-            One,
-            Two,
-            Three,
-        };
+    try testFmt("TU{ .int = 123 }", "{}", tu_inst);
 
-        const inst = E.Two;
+    var buf: [100]u8 = undefined;
+    const uu_result = try bufPrint(buf[0..], "{}", uu_inst);
+    testing.expect(mem.eql(u8, uu_result[0..3], "UU@"));
 
-        try testFmt("E.Two", "{}", inst);
-    }
-    //self-referential struct format
-    {
-        const S = struct {
-            const SelfType = @This();
-            a: ?*SelfType,
-        };
+    const eu_result = try bufPrint(buf[0..], "{}", eu_inst);
+    testing.expect(mem.eql(u8, uu_result[0..3], "EU@"));
+}
 
-        var inst = S{
-            .a = null,
-        };
-        inst.a = &inst;
+test "fmt.enum" {
+    const E = enum {
+        One,
+        Two,
+        Three,
+    };
 
-        try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst);
-    }
-    //print bytes as hex
-    {
-        const some_bytes = "\xCA\xFE\xBA\xBE";
-        try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes);
-        try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes);
-        //Test Slices
-        try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", some_bytes[0..2]);
-        try testFmt("lowercase: babe\n", "lowercase: {x}\n", some_bytes[2..]);
-        const bytes_with_zeros = "\x00\x0E\xBA\xBE";
-        try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", bytes_with_zeros);
-    }
+    const inst = E.Two;
+
+    try testFmt("E.Two", "{}", inst);
+}
+
+test "fmt.struct.self-referential" {
+    const S = struct {
+        const SelfType = @This();
+        a: ?*SelfType,
+    };
+
+    var inst = S{
+        .a = null,
+    };
+    inst.a = &inst;
+
+    try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst);
+}
+
+test "fmt.bytes.hex" {
+    const some_bytes = "\xCA\xFE\xBA\xBE";
+    try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes);
+    try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes);
+    //Test Slices
+    try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", some_bytes[0..2]);
+    try testFmt("lowercase: babe\n", "lowercase: {x}\n", some_bytes[2..]);
+    const bytes_with_zeros = "\x00\x0E\xBA\xBE";
+    try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", bytes_with_zeros);
 }
 
 fn testFmt(expected: []const u8, comptime template: []const u8, args: ...) !void {