authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-28 17:38:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:28-07:00
logaef0434c014d85d4f5ab8afa931ea1848c8bbd16
treefe02afbbb88b9c071ea570cf7ff1ae508fb17fcf
parentd8cea032455228a12e3413068aff46b06fb02a59

formatted printing: fix handling of nested format functions


2 files changed, 86 insertions(+), 87 deletions(-)

lib/std/io/BufferedWriter.zig+84-82
......@@ -136,6 +136,15 @@ pub fn writableSliceGreedy(bw: *BufferedWriter, minimum_length: usize) Writer.Er
136136 return bw.buffer[bw.end..];
137137}
138138
139pub fn ensureUnusedCapacity(bw: *BufferedWriter, n: usize) Writer.Error!void {
140 _ = try writableSliceGreedy(bw, n);
141}
142
143pub fn undo(bw: *BufferedWriter, n: usize) void {
144 bw.end -= n;
145 bw.count -= n;
146}
147
139148/// After calling `writableSliceGreedy`, this function tracks how many bytes
140149/// were written to it.
141150///
......@@ -797,18 +806,13 @@ pub fn printValue(
797806 max_depth: usize,
798807) Writer.Error!void {
799808 const T = @TypeOf(value);
800 const actual_fmt = comptime if (std.mem.eql(u8, fmt, ANY))
801 defaultFormatString(T)
802 else if (fmt.len != 0 and (fmt[0] == '?' or fmt[0] == '!')) switch (@typeInfo(T)) {
803 .optional, .error_union => fmt,
804 else => stripOptionalOrErrorUnionSpec(fmt),
805 } else fmt;
806
807 if (comptime std.mem.eql(u8, actual_fmt, "*")) {
809
810 if (comptime std.mem.eql(u8, fmt, "*")) {
808811 return bw.printAddress(value);
809812 }
810813
811 if (std.meta.hasMethod(T, "format")) {
814 const is_any = comptime std.mem.eql(u8, fmt, ANY);
815 if (!is_any and std.meta.hasMethod(T, "format")) {
812816 if (fmt.len > 0 and fmt[0] == 'f') {
813817 return value.format(bw, fmt[1..]);
814818 } else if (fmt.len == 0) {
......@@ -818,20 +822,23 @@ pub fn printValue(
818822 }
819823
820824 switch (@typeInfo(T)) {
821 .float, .comptime_float => return bw.printFloat(actual_fmt, options, value),
822 .int, .comptime_int => return bw.printInt(actual_fmt, options, value),
825 .float, .comptime_float => return bw.printFloat(if (is_any) "d" else fmt, options, value),
826 .int, .comptime_int => return bw.printInt(if (is_any) "d" else fmt, options, value),
823827 .bool => {
824 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
828 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
825829 return bw.alignBufferOptions(if (value) "true" else "false", options);
826830 },
827831 .void => {
828 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
832 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
829833 return bw.alignBufferOptions("void", options);
830834 },
831835 .optional => {
832 if (actual_fmt.len == 0 or actual_fmt[0] != '?')
836 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
837 stripOptionalOrErrorUnionSpec(fmt)
838 else if (is_any)
839 ANY
840 else
833841 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
834 const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt);
835842 if (value) |payload| {
836843 return bw.printValue(remaining_fmt, options, payload, max_depth);
837844 } else {
......@@ -839,9 +846,12 @@ pub fn printValue(
839846 }
840847 },
841848 .error_union => {
842 if (actual_fmt.len == 0 or actual_fmt[0] != '!')
843 @compileError("cannot format error union without a specifier (i.e. {!} or {any})");
844 const remaining_fmt = comptime stripOptionalOrErrorUnionSpec(actual_fmt);
849 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
850 stripOptionalOrErrorUnionSpec(fmt)
851 else if (is_any)
852 ANY
853 else
854 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
845855 if (value) |payload| {
846856 return bw.printValue(remaining_fmt, options, payload, max_depth);
847857 } else |err| {
......@@ -849,40 +859,43 @@ pub fn printValue(
849859 }
850860 },
851861 .error_set => {
852 if (actual_fmt.len > 0 and actual_fmt[0] == 's') {
853 return bw.writeAll(@errorName(value));
854 } else if (actual_fmt.len != 0) {
855 invalidFmtError(fmt, value);
856 } else {
857 try bw.writeAll("error.");
858 try bw.writeAll(@errorName(value));
859 }
862 if (fmt.len == 1 and fmt[0] == 's') return bw.writeAll(@errorName(value));
863 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
864 try printErrorSet(bw, value);
860865 },
861 .@"enum" => |enum_info| {
862 try bw.writeAll(@typeName(T));
863 if (enum_info.is_exhaustive) {
864 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
865 try bw.writeAll(".");
866 .@"enum" => {
867 if (fmt.len == 1 and fmt[0] == 's') {
866868 try bw.writeAll(@tagName(value));
867869 return;
868870 }
869
870 // Use @tagName only if value is one of known fields
871 if (!is_any) {
872 if (fmt.len != 0) return printValue(bw, fmt, options, @intFromEnum(value), max_depth);
873 return printValue(bw, ANY, options, value, max_depth);
874 }
875 const enum_info = @typeInfo(T).@"enum";
876 if (enum_info.is_exhaustive) {
877 var vecs: [3][]const u8 = .{ @typeName(T), ".", @tagName(value) };
878 try bw.writeVecAll(&vecs);
879 return;
880 }
881 try bw.writeAll(@typeName(T));
871882 @setEvalBranchQuota(3 * enum_info.fields.len);
872 inline for (enum_info.fields) |enumField| {
873 if (@intFromEnum(value) == enumField.value) {
883 inline for (enum_info.fields) |field| {
884 if (@intFromEnum(value) == field.value) {
874885 try bw.writeAll(".");
875886 try bw.writeAll(@tagName(value));
876887 return;
877888 }
878889 }
879
880890 try bw.writeByte('(');
881 try bw.printValue(actual_fmt, options, @intFromEnum(value), max_depth);
891 try bw.printValue(ANY, options, @intFromEnum(value), max_depth);
882892 try bw.writeByte(')');
883893 },
884894 .@"union" => |info| {
885 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
895 if (!is_any) {
896 if (fmt.len != 0) invalidFmtError(fmt, value);
897 return printValue(bw, ANY, options, value, max_depth);
898 }
886899 try bw.writeAll(@typeName(T));
887900 if (max_depth == 0) {
888901 try bw.writeAll("{ ... }");
......@@ -904,7 +917,10 @@ pub fn printValue(
904917 }
905918 },
906919 .@"struct" => |info| {
907 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
920 if (!is_any) {
921 if (fmt.len != 0) invalidFmtError(fmt, value);
922 return printValue(bw, ANY, options, value, max_depth);
923 }
908924 if (info.is_tuple) {
909925 // Skip the type and field names when formatting tuples.
910926 if (max_depth == 0) {
......@@ -944,7 +960,7 @@ pub fn printValue(
944960 .pointer => |ptr_info| switch (ptr_info.size) {
945961 .one => switch (@typeInfo(ptr_info.child)) {
946962 .array, .@"enum", .@"union", .@"struct" => {
947 return bw.printValue(actual_fmt, options, value.*, max_depth);
963 return bw.printValue(fmt, options, value.*, max_depth);
948964 },
949965 else => {
950966 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
......@@ -954,37 +970,36 @@ pub fn printValue(
954970 },
955971 },
956972 .many, .c => {
957 if (actual_fmt.len == 0)
958 @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
959 if (ptr_info.sentinel() != null) {
960 return bw.printValue(actual_fmt, options, std.mem.span(value), max_depth);
961 }
962 if (actual_fmt[0] == 's' and ptr_info.child == u8) {
973 if (ptr_info.sentinel() != null)
974 return bw.printValue(fmt, options, std.mem.span(value), max_depth);
975 if (fmt.len == 1 and fmt[0] == 's' and ptr_info.child == u8)
963976 return bw.alignBufferOptions(std.mem.span(value), options);
964 }
965 invalidFmtError(fmt, value);
977 if (!is_any and fmt.len == 0)
978 @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
979 if (!is_any and fmt.len != 0)
980 invalidFmtError(fmt, value);
981 try bw.printAddress(value);
966982 },
967983 .slice => {
968 if (actual_fmt.len == 0)
984 if (!is_any and fmt.len == 0)
969985 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
970 if (max_depth == 0) {
986 if (max_depth == 0)
971987 return bw.writeAll("{ ... }");
972 }
973 if (ptr_info.child == u8) switch (actual_fmt.len) {
974 1 => switch (actual_fmt[0]) {
988 if (ptr_info.child == u8) switch (fmt.len) {
989 1 => switch (fmt[0]) {
975990 's' => return bw.alignBufferOptions(value, options),
976991 'x' => return bw.printHex(value, .lower),
977992 'X' => return bw.printHex(value, .upper),
978993 else => {},
979994 },
980 3 => if (actual_fmt[0] == 'b' and actual_fmt[1] == '6' and actual_fmt[2] == '4') {
995 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') {
981996 return bw.printBase64(value);
982997 },
983998 else => {},
984999 };
9851000 try bw.writeAll("{ ");
9861001 for (value, 0..) |elem, i| {
987 try bw.printValue(actual_fmt, options, elem, max_depth - 1);
1002 try bw.printValue(fmt, options, elem, max_depth - 1);
9881003 if (i != value.len - 1) {
9891004 try bw.writeAll(", ");
9901005 }
......@@ -993,23 +1008,23 @@ pub fn printValue(
9931008 },
9941009 },
9951010 .array => |info| {
996 if (actual_fmt.len == 0)
1011 if (fmt.len == 0)
9971012 @compileError("cannot format array without a specifier (i.e. {s} or {any})");
9981013 if (max_depth == 0) {
9991014 return bw.writeAll("{ ... }");
10001015 }
10011016 if (info.child == u8) {
1002 if (actual_fmt[0] == 's') {
1017 if (fmt[0] == 's') {
10031018 return bw.alignBufferOptions(&value, options);
1004 } else if (actual_fmt[0] == 'x') {
1019 } else if (fmt[0] == 'x') {
10051020 return bw.printHex(&value, .lower);
1006 } else if (actual_fmt[0] == 'X') {
1021 } else if (fmt[0] == 'X') {
10071022 return bw.printHex(&value, .upper);
10081023 }
10091024 }
10101025 try bw.writeAll("{ ");
10111026 for (value, 0..) |elem, i| {
1012 try bw.printValue(actual_fmt, options, elem, max_depth - 1);
1027 try bw.printValue(fmt, options, elem, max_depth - 1);
10131028 if (i < value.len - 1) {
10141029 try bw.writeAll(", ");
10151030 }
......@@ -1023,7 +1038,7 @@ pub fn printValue(
10231038 try bw.writeAll("{ ");
10241039 var i: usize = 0;
10251040 while (i < info.len) : (i += 1) {
1026 try bw.printValue(actual_fmt, options, value[i], max_depth - 1);
1041 try bw.printValue(fmt, options, value[i], max_depth - 1);
10271042 if (i < info.len - 1) {
10281043 try bw.writeAll(", ");
10291044 }
......@@ -1032,22 +1047,27 @@ pub fn printValue(
10321047 },
10331048 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
10341049 .type => {
1035 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
1050 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
10361051 return bw.alignBufferOptions(@typeName(value), options);
10371052 },
10381053 .enum_literal => {
1039 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
1054 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
10401055 const buffer = [_]u8{'.'} ++ @tagName(value);
10411056 return bw.alignBufferOptions(buffer, options);
10421057 },
10431058 .null => {
1044 if (actual_fmt.len != 0) invalidFmtError(fmt, value);
1059 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
10451060 return bw.alignBufferOptions("null", options);
10461061 },
10471062 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
10481063 }
10491064}
10501065
1066fn printErrorSet(bw: *BufferedWriter, error_set: anyerror) Writer.Error!void {
1067 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1068 try bw.writeVecAll(&vecs);
1069}
1070
10511071pub fn printInt(
10521072 bw: *BufferedWriter,
10531073 comptime fmt: []const u8,
......@@ -1376,24 +1396,6 @@ pub fn printByteSize(
13761396// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
13771397const ANY = "any";
13781398
1379fn defaultFormatString(comptime T: type) [:0]const u8 {
1380 switch (@typeInfo(T)) {
1381 .array, .vector => return ANY,
1382 .pointer => |ptr_info| switch (ptr_info.size) {
1383 .one => switch (@typeInfo(ptr_info.child)) {
1384 .array => return ANY,
1385 else => {},
1386 },
1387 .many, .c => return "*",
1388 .slice => return ANY,
1389 },
1390 .optional => |info| return "?" ++ defaultFormatString(info.child),
1391 .error_union => |info| return "!" ++ defaultFormatString(info.payload),
1392 else => {},
1393 }
1394 return "";
1395}
1396
13971399fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
13981400 return if (std.mem.eql(u8, fmt[1..], ANY))
13991401 ANY
lib/std/testing.zig+2-5
......@@ -52,14 +52,11 @@ fn print(comptime fmt: []const u8, args: anytype) void {
5252/// and then returns a test failure error when actual_error_union is not expected_error.
5353pub fn expectError(expected_error: anyerror, actual_error_union: anytype) !void {
5454 if (actual_error_union) |actual_payload| {
55 print("expected error.{s}, found {any}\n", .{ @errorName(expected_error), actual_payload });
55 print("expected {s}, found {any}\n", .{ expected_error, actual_payload });
5656 return error.TestExpectedError;
5757 } else |actual_error| {
5858 if (expected_error != actual_error) {
59 print("expected error.{s}, found error.{s}\n", .{
60 @errorName(expected_error),
61 @errorName(actual_error),
62 });
59 print("expected {s}, found {s}\n", .{ expected_error, actual_error });
6360 return error.TestUnexpectedError;
6461 }
6562 }