From ea151030bc4a1366ef3ca0bd1f59d4867a215762 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 25 May 2026 12:07:21 -0700 Subject: [PATCH] std.Io.Writer: implement {q} formatter This is intended to replace the common pattern: print("invalid foobar: '{s}': {t}", ...) The idea is to not invent imaginary syntax. If the string contained single quotes for example, this would be a nonsensical error message. On the other hand with the new pattern: print("invalid foobar: {q}: {t}", ...) It's both easier on the eyes at the print site, and also it will allow the user to copy paste a properly escaped string, should the quoted text contain any odd characters, including invisible ones like null bytes. --- lib/std/Io/Writer.zig | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/std/Io/Writer.zig b/lib/std/Io/Writer.zig index c489a5c93d6ef3b4adcf94a2eca16daff8a56eee..2aef4f8826aaa7a6657c4e41bba675f6eb400179 100644 --- a/lib/std/Io/Writer.zig +++ b/lib/std/Io/Writer.zig @@ -1172,23 +1172,12 @@ pub fn printValue( }, else => invalidFmtError(fmt, value), }, - // TODO make this print double quotes and quote-escape the string - // according to zig string syntax rules 'q' => switch (@typeInfo(T)) { .pointer => |info| switch (info.size) { - .one, .slice => { - const slice: []const u8 = value; - return w.alignBufferOptions(slice, options); - }, - .many, .c => { - const slice: [:0]const u8 = std.mem.span(value); - return w.alignBufferOptions(slice, options); - }, - }, - .array => { - const slice: []const u8 = &value; - return w.alignBufferOptions(slice, options); + .one, .slice => return printStringEscaped(w, value), + .many, .c => return printStringEscaped(w, std.mem.span(value)), }, + .array => return printStringEscaped(w, &value), else => invalidFmtError(fmt, value), }, 'B' => switch (@typeInfo(T)) { @@ -1467,6 +1456,14 @@ fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { try w.writeByte(')'); } +/// Prints a double quote, then escapes a string according to Zig string +/// literal rules, then a double quote. +pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void { + try w.writeByte('"'); + try std.zig.stringEscape(bytes, w); + try w.writeByte('"'); +} + pub fn printVector( w: *Writer, comptime fmt: []const u8, @@ -2121,6 +2118,11 @@ test "printFloat with comptime_float" { try testing.expectFmt("1", "{}", .{1.0}); } +test "{q} format string" { + const data: []const u8 = "i\tlike\"cheese\x00\x05cheese"; + try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data}); +} + fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { var buffer: [100]u8 = undefined; var w: Writer = .fixed(&buffer); -- 2.54.0