From fced9467e8eaa85315154f9567cf6da23246d1ef Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 16 Feb 2025 22:02:45 -0800 Subject: [PATCH] std ArrayList unit tests passing --- lib/std/Build/Step/CheckObject.zig | 24 ++-- lib/std/Build/Step/Compile.zig | 22 +-- lib/std/Uri.zig | 14 +- lib/std/array_list.zig | 54 ------- lib/std/compress/lzma2.zig | 15 +- lib/std/compress/zstandard/decode/block.zig | 6 +- lib/std/compress/zstandard/decode/huffman.zig | 4 +- lib/std/compress/zstandard/decompress.zig | 8 +- lib/std/debug.zig | 56 ++++---- lib/std/fs/File.zig | 5 + lib/std/http/Client.zig | 24 ++-- lib/std/io/AllocatingWriter.zig | 19 ++- lib/std/io/CountingWriter.zig | 3 + lib/std/testing.zig | 51 +++---- lib/std/zig/ErrorBundle.zig | 133 +++++++++--------- 15 files changed, 208 insertions(+), 230 deletions(-) diff --git a/lib/std/Build/Step/CheckObject.zig b/lib/std/Build/Step/CheckObject.zig index 7ce495f936..324abf3c90 100644 --- a/lib/std/Build/Step/CheckObject.zig +++ b/lib/std/Build/Step/CheckObject.zig @@ -1242,7 +1242,7 @@ const MachODumper = struct { } fn parseRebaseInfo(ctx: ObjectContext, data: []const u8, rebases: *std.ArrayList(u64)) !void { - var stream = std.io.fixedBufferStream(data); + var stream: std.io.FixedBufferStream = .{ .buffer = data }; var creader = std.io.countingReader(stream.reader()); const reader = creader.reader(); @@ -1354,7 +1354,7 @@ const MachODumper = struct { } fn parseBindInfo(ctx: ObjectContext, data: []const u8, bindings: *std.ArrayList(Binding)) !void { - var stream = std.io.fixedBufferStream(data); + var stream: std.io.FixedBufferStream = .{ .buffer = data }; var creader = std.io.countingReader(stream.reader()); const reader = creader.reader(); @@ -1487,8 +1487,8 @@ const MachODumper = struct { data: []const u8, pos: usize = 0, - fn getStream(it: *TrieIterator) std.io.FixedBufferStream([]const u8) { - return std.io.fixedBufferStream(it.data[it.pos..]); + fn getStream(it: *TrieIterator) std.io.FixedBufferStream { + return .{ .buffer = it.data[it.pos..] }; } fn readUleb128(it: *TrieIterator) !u64 { @@ -1748,7 +1748,7 @@ const ElfDumper = struct { fn parseAndDumpArchive(step: *Step, check: Check, bytes: []const u8) ![]const u8 { const gpa = step.owner.allocator; - var stream = std.io.fixedBufferStream(bytes); + var stream: std.io.FixedBufferStream = .{ .buffer = bytes }; const reader = stream.reader(); const magic = try reader.readBytesNoEof(elf.ARMAG.len); @@ -1805,8 +1805,8 @@ const ElfDumper = struct { try ctx.objects.append(gpa, .{ .name = name, .off = stream.pos, .len = size }); } - var output = std.ArrayList(u8).init(gpa); - const writer = output.writer(); + var output: std.io.AllocatingWriter = undefined; + const writer = output.init(gpa); switch (check.kind) { .archive_symtab => if (ctx.symtab.items.len > 0) { @@ -1829,7 +1829,7 @@ const ElfDumper = struct { objects: std.ArrayListUnmanaged(struct { name: []const u8, off: usize, len: usize }) = .empty, fn parseSymtab(ctx: *ArchiveContext, raw: []const u8, ptr_width: enum { p32, p64 }) !void { - var stream = std.io.fixedBufferStream(raw); + var stream: std.io.FixedBufferStream = .{ .buffer = raw }; const reader = stream.reader(); const num = switch (ptr_width) { .p32 => try reader.readInt(u32, .big), @@ -1914,7 +1914,7 @@ const ElfDumper = struct { fn parseAndDumpObject(step: *Step, check: Check, bytes: []const u8) ![]const u8 { const gpa = step.owner.allocator; - var stream = std.io.fixedBufferStream(bytes); + var stream: std.io.FixedBufferStream = .{ .buffer = bytes }; const reader = stream.reader(); const hdr = try reader.readStruct(elf.Elf64_Ehdr); @@ -2419,7 +2419,7 @@ const WasmDumper = struct { fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 { const gpa = step.owner.allocator; - var fbs = std.io.fixedBufferStream(bytes); + var fbs: std.io.FixedBufferStream = .{ .buffer = bytes }; const reader = fbs.reader(); const buf = try reader.readBytesNoEof(8); @@ -2472,7 +2472,7 @@ const WasmDumper = struct { data: []const u8, bw: *std.io.BufferedWriter, ) !void { - var fbs = std.io.fixedBufferStream(data); + var fbs: std.io.FixedBufferStream = .{ .buffer = data }; const reader = fbs.reader(); try bw.print( @@ -2524,7 +2524,7 @@ const WasmDumper = struct { } fn parseSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, bw: *std.io.BufferedWriter) !void { - var fbs = std.io.fixedBufferStream(data); + var fbs: std.io.FixedBufferStream = .{ .buffer = data }; const reader = fbs.reader(); switch (section) { diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 2333e14e09..d48b70e0dc 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1964,20 +1964,24 @@ fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) fn checkCompileErrors(compile: *Compile) !void { // Clear this field so that it does not get printed by the build runner. const actual_eb = compile.step.result_error_bundle; - compile.step.result_error_bundle = std.zig.ErrorBundle.empty; + compile.step.result_error_bundle = .empty; const arena = compile.step.owner.allocator; - var actual_errors_list = std.ArrayList(u8).init(arena); - try actual_eb.renderToWriter(.{ - .ttyconf = .no_color, - .include_reference_trace = false, - .include_source_line = false, - }, actual_errors_list.writer()); - const actual_errors = try actual_errors_list.toOwnedSlice(); + const actual_errors = ae: { + var aw: std.io.AllocatingWriter = undefined; + const bw = aw.init(arena); + defer aw.deinit(); + try actual_eb.renderToWriter(.{ + .ttyconf = .no_color, + .include_reference_trace = false, + .include_source_line = false, + }, bw); + break :ae try aw.toOwnedSlice(); + }; // Render the expected lines into a string that we can compare verbatim. - var expected_generated = std.ArrayList(u8).init(arena); + var expected_generated: std.ArrayListUnmanaged(u8) = .empty; const expect_errors = compile.expect_errors.?; var actual_line_it = mem.splitScalar(u8, actual_errors, '\n'); diff --git a/lib/std/Uri.zig b/lib/std/Uri.zig index ee0c602125..2d3dbb145c 100644 --- a/lib/std/Uri.zig +++ b/lib/std/Uri.zig @@ -34,7 +34,7 @@ pub const Component = union(enum) { return switch (component) { .raw => |raw| raw, .percent_encoded => |percent_encoded| if (std.mem.indexOfScalar(u8, percent_encoded, '%')) |_| - try std.fmt.allocPrint(arena, "{raw}", .{component}) + try std.fmt.allocPrint(arena, "{fraw}", .{component}) else percent_encoded, }; @@ -44,8 +44,8 @@ pub const Component = union(enum) { component: Component, comptime fmt_str: []const u8, _: std.fmt.FormatOptions, - writer: anytype, - ) @TypeOf(writer).Error!void { + writer: *std.io.BufferedWriter, + ) anyerror!void { if (fmt_str.len == 0) { try writer.print("std.Uri.Component{{ .{s} = \"{}\" }}", .{ @tagName(component), @@ -97,10 +97,10 @@ pub const Component = union(enum) { } pub fn percentEncode( - writer: anytype, + writer: *std.io.BufferedWriter, raw: []const u8, comptime isValidChar: fn (u8) bool, - ) @TypeOf(writer).Error!void { + ) anyerror!void { var start: usize = 0; for (raw, 0..) |char, index| { if (isValidChar(char)) continue; @@ -822,7 +822,7 @@ test "URI percent decoding" { const expected = "\\ö/ äöß ~~.adas-https://canvas:123/#ads&&sad"; var input = "%5C%C3%B6%2F%20%C3%A4%C3%B6%C3%9F%20~~.adas-https%3A%2F%2Fcanvas%3A123%2F%23ads%26%26sad".*; - try std.testing.expectFmt(expected, "{raw}", .{Component{ .percent_encoded = &input }}); + try std.testing.expectFmt(expected, "{fraw}", .{Component{ .percent_encoded = &input }}); var output: [expected.len]u8 = undefined; try std.testing.expectEqualStrings(percentDecodeBackwards(&output, &input), expected); @@ -834,7 +834,7 @@ test "URI percent decoding" { const expected = "/abc%"; var input = expected.*; - try std.testing.expectFmt(expected, "{raw}", .{Component{ .percent_encoded = &input }}); + try std.testing.expectFmt(expected, "{fraw}", .{Component{ .percent_encoded = &input }}); var output: [expected.len]u8 = undefined; try std.testing.expectEqualStrings(percentDecodeBackwards(&output, &input), expected); diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 8adeea3e40..4c5b0b3525 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -1828,60 +1828,6 @@ test "ArrayList(T) of struct T" { } } -test "ArrayList(u8) implements writer" { - const a = testing.allocator; - - { - var buffer = ArrayList(u8).init(a); - defer buffer.deinit(); - - const x: i32 = 42; - const y: i32 = 1234; - try buffer.writer().print("x: {}\ny: {}\n", .{ x, y }); - - try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); - } - { - var list = ArrayListAligned(u8, .@"2").init(a); - defer list.deinit(); - - const writer = list.writer(); - try writer.writeAll("a"); - try writer.writeAll("bc"); - try writer.writeAll("d"); - try writer.writeAll("efg"); - - try testing.expectEqualSlices(u8, list.items, "abcdefg"); - } -} - -test "ArrayListUnmanaged(u8) implements writer" { - const a = testing.allocator; - - { - var buffer: ArrayListUnmanaged(u8) = .empty; - defer buffer.deinit(a); - - const x: i32 = 42; - const y: i32 = 1234; - try buffer.writer(a).print("x: {}\ny: {}\n", .{ x, y }); - - try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); - } - { - var list: ArrayListAlignedUnmanaged(u8, .@"2") = .empty; - defer list.deinit(a); - - const writer = list.writer(a); - try writer.writeAll("a"); - try writer.writeAll("bc"); - try writer.writeAll("d"); - try writer.writeAll("efg"); - - try testing.expectEqualSlices(u8, list.items, "abcdefg"); - } -} - test "shrink still sets length when resizing is disabled" { var failing_allocator = testing.FailingAllocator.init(testing.allocator, .{ .resize_fail_index = 0 }); const a = failing_allocator.allocator(); diff --git a/lib/std/compress/lzma2.zig b/lib/std/compress/lzma2.zig index 2797990f9c..036a0d879b 100644 --- a/lib/std/compress/lzma2.zig +++ b/lib/std/compress/lzma2.zig @@ -15,12 +15,15 @@ pub fn decompress( test { const expected = "Hello\nWorld!\n"; - const compressed = &[_]u8{ 0x01, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0A, 0x02, 0x00, 0x06, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x0A, 0x00 }; + const compressed = &[_]u8{ + 0x01, 0x00, 0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x0A, 0x02, + 0x00, 0x06, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x0A, 0x00, + }; + var stream: std.io.FixedBufferStream = .{ .buffer = compressed }; - const allocator = std.testing.allocator; - var decomp = std.ArrayList(u8).init(allocator); + var decomp: std.io.AllocatingWriter = undefined; + const decomp_bw = decomp.init(std.testing.allocator); defer decomp.deinit(); - var stream = std.io.fixedBufferStream(compressed); - try decompress(allocator, stream.reader(), decomp.writer()); - try std.testing.expectEqualSlices(u8, expected, decomp.items); + try decompress(std.testing.allocator, stream.reader(), decomp_bw); + try std.testing.expectEqualSlices(u8, expected, decomp.getWritten()); } diff --git a/lib/std/compress/zstandard/decode/block.zig b/lib/std/compress/zstandard/decode/block.zig index 49c6e7dc36..e71a20e56b 100644 --- a/lib/std/compress/zstandard/decode/block.zig +++ b/lib/std/compress/zstandard/decode/block.zig @@ -631,7 +631,7 @@ pub fn decodeBlock( var bytes_read: usize = 0; const literals = decodeLiteralsSectionSlice(src[0..block_size], &bytes_read) catch return error.MalformedCompressedBlock; - var fbs = std.io.fixedBufferStream(src[bytes_read..block_size]); + var fbs: std.io.FixedBufferStream = .{ .buffer = src[bytes_read..block_size] }; const fbs_reader = fbs.reader(); const sequences_header = decodeSequencesHeader(fbs_reader) catch return error.MalformedCompressedBlock; @@ -737,7 +737,7 @@ pub fn decodeBlockRingBuffer( var bytes_read: usize = 0; const literals = decodeLiteralsSectionSlice(src[0..block_size], &bytes_read) catch return error.MalformedCompressedBlock; - var fbs = std.io.fixedBufferStream(src[bytes_read..block_size]); + var fbs: std.io.FixedBufferStream = .{ .buffer = src[bytes_read..block_size] }; const fbs_reader = fbs.reader(); const sequences_header = decodeSequencesHeader(fbs_reader) catch return error.MalformedCompressedBlock; @@ -931,7 +931,7 @@ pub fn decodeLiteralsSectionSlice( ) (error{ MalformedLiteralsHeader, MalformedLiteralsSection, EndOfStream } || huffman.Error)!LiteralsSection { var bytes_read: usize = 0; const header = header: { - var fbs = std.io.fixedBufferStream(src); + var fbs: std.io.FixedBufferStream = .{ .buffer = src }; defer bytes_read = fbs.pos; break :header decodeLiteralsHeader(fbs.reader()) catch return error.MalformedLiteralsHeader; }; diff --git a/lib/std/compress/zstandard/decode/huffman.zig b/lib/std/compress/zstandard/decode/huffman.zig index 4728ccd027..0a5324e9d1 100644 --- a/lib/std/compress/zstandard/decode/huffman.zig +++ b/lib/std/compress/zstandard/decode/huffman.zig @@ -41,7 +41,7 @@ fn decodeFseHuffmanTree( fn decodeFseHuffmanTreeSlice(src: []const u8, compressed_size: usize, weights: *[256]u4) !usize { if (src.len < compressed_size) return error.MalformedHuffmanTree; - var stream = std.io.fixedBufferStream(src[0..compressed_size]); + var stream: std.io.FixedBufferStream = .{ .buffer = src[0..compressed_size] }; var counting_reader = std.io.countingReader(stream.reader()); var bit_reader = readers.bitReader(counting_reader.reader()); @@ -213,7 +213,7 @@ pub fn decodeHuffmanTreeSlice( bytes_read += header; break :count try decodeFseHuffmanTreeSlice(src[1..], header, &weights); } else count: { - var fbs = std.io.fixedBufferStream(src[1..]); + var fbs: std.io.FixedBufferStream = .{ .buffer = src[1..] }; defer bytes_read += fbs.pos; break :count try decodeDirectHuffmanTree(fbs.reader(), header - 127, &weights); }; diff --git a/lib/std/compress/zstandard/decompress.zig b/lib/std/compress/zstandard/decompress.zig index adc7b89749..3529556ee9 100644 --- a/lib/std/compress/zstandard/decompress.zig +++ b/lib/std/compress/zstandard/decompress.zig @@ -186,7 +186,7 @@ pub fn decodeFrame( DictionaryIdFlagUnsupported, SkippableSizeTooLarge, } || FrameError)!ReadWriteCount { - var fbs = std.io.fixedBufferStream(src); + var fbs: std.io.FixedBufferStream = .{ .buffer = src }; switch (try decodeFrameType(fbs.reader())) { .zstandard => return decodeZstandardFrame(dest, src, verify_checksum), .skippable => { @@ -233,7 +233,7 @@ pub fn decodeFrameArrayList( verify_checksum: bool, window_size_max: usize, ) (error{ BadMagic, OutOfMemory, SkippableSizeTooLarge } || FrameContext.Error || FrameError)!usize { - var fbs = std.io.fixedBufferStream(src); + var fbs: std.io.FixedBufferStream = .{ .buffer = src }; const reader = fbs.reader(); const magic = try reader.readInt(u32, .little); switch (try frameType(magic)) { @@ -303,7 +303,7 @@ pub fn decodeZstandardFrame( var consumed_count: usize = 4; var frame_context = context: { - var fbs = std.io.fixedBufferStream(src[consumed_count..]); + var fbs: std.io.FixedBufferStream = .{ .buffer = src[consumed_count..] }; const source = fbs.reader(); const frame_header = try decodeZstandardHeader(source); consumed_count += fbs.pos; @@ -446,7 +446,7 @@ pub fn decodeZstandardFrameArrayList( var consumed_count: usize = 4; var frame_context = context: { - var fbs = std.io.fixedBufferStream(src[consumed_count..]); + var fbs: std.io.FixedBufferStream = .{ .buffer = src[consumed_count..] }; const source = fbs.reader(); const frame_header = try decodeZstandardHeader(source); consumed_count += fbs.pos; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 2efcae2538..7614492fed 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -242,50 +242,44 @@ pub fn getSelfDebugInfo() !*SelfInfo { /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. /// Obtains the stderr mutex while dumping. pub fn dumpHex(bytes: []const u8) void { - lockStdErr(); + var bw = lockStdErr2(); defer unlockStdErr(); - dumpHexFallible(bytes) catch {}; + const ttyconf = std.io.tty.detectConfig(std.io.getStdErr()); + dumpHexFallible(&bw, ttyconf, bytes) catch {}; } -/// Prints a hexadecimal view of the bytes, unbuffered, returning any error that occurs. -pub fn dumpHexFallible(bytes: []const u8) !void { - const stderr = std.io.getStdErr(); - const ttyconf = std.io.tty.detectConfig(stderr); - const writer = stderr.writer(); - try dumpHexInternal(bytes, ttyconf, writer); -} - -fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytype) !void { +/// Prints a hexadecimal view of the bytes, returning any error that occurs. +pub fn dumpHexFallible(bw: *std.io.BufferedWriter, ttyconf: std.io.tty.Config, bytes: []const u8) !void { var chunks = mem.window(u8, bytes, 16, 16); while (chunks.next()) |window| { // 1. Print the address. const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; - try ttyconf.setColor(writer, .dim); + try ttyconf.setColor(bw, .dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. - try writer.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); - try ttyconf.setColor(writer, .reset); + try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); + try ttyconf.setColor(bw, .reset); // 2. Print the bytes. for (window, 0..) |byte, index| { - try writer.print("{X:0>2} ", .{byte}); - if (index == 7) try writer.writeByte(' '); + try bw.print("{X:0>2} ", .{byte}); + if (index == 7) try bw.writeByte(' '); } - try writer.writeByte(' '); + try bw.writeByte(' '); if (window.len < 16) { var missing_columns = (16 - window.len) * 3; if (window.len < 8) missing_columns += 1; - try writer.splatByteAll(' ', missing_columns); + try bw.splatByteAll(' ', missing_columns); } // 3. Print the characters. for (window) |byte| { if (std.ascii.isPrint(byte)) { - try writer.writeByte(byte); + try bw.writeByte(byte); } else { // Related: https://github.com/ziglang/zig/issues/7600 if (ttyconf == .windows_api) { - try writer.writeByte('.'); + try bw.writeByte('.'); continue; } @@ -293,22 +287,24 @@ fn dumpHexInternal(bytes: []const u8, ttyconf: std.io.tty.Config, writer: anytyp // We don't want to do this for all control codes because most control codes apart from // the ones that Zig has escape sequences for are likely not very useful to print as symbols. switch (byte) { - '\n' => try writer.writeAll("␊"), - '\r' => try writer.writeAll("␍"), - '\t' => try writer.writeAll("␉"), - else => try writer.writeByte('.'), + '\n' => try bw.writeAll("␊"), + '\r' => try bw.writeAll("␍"), + '\t' => try bw.writeAll("␉"), + else => try bw.writeByte('.'), } } } - try writer.writeByte('\n'); + try bw.writeByte('\n'); } } -test dumpHexInternal { +test dumpHexFallible { const bytes: []const u8 = &.{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x12, 0x13 }; - var output = std.ArrayList(u8).init(std.testing.allocator); - defer output.deinit(); - try dumpHexInternal(bytes, .no_color, output.writer()); + var aw: std.io.AllocatingWriter = undefined; + defer aw.deinit(); + var bw = aw.init(std.testing.allocator); + + try dumpHexFallible(&bw, .no_color, bytes); const expected = try std.fmt.allocPrint(std.testing.allocator, \\{x:0>[2]} 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ \\{x:0>[2]} 01 12 13 ... @@ -319,7 +315,7 @@ test dumpHexInternal { @sizeOf(usize) * 2, }); defer std.testing.allocator.free(expected); - try std.testing.expectEqualStrings(expected, output.items); + try std.testing.expectEqualStrings(expected, aw.getWritten()); } /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index f5901cea1c..872b9ba021 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -1496,6 +1496,11 @@ pub fn writeFileAll(self: File, in_file: File, args: WriteFileOptions) WriteFile /// Does not try seeking in either of the File parameters. /// See `writeFileAll` as an alternative to calling this. pub fn writeFileAllUnseekable(self: File, in_file: File, args: WriteFileOptions) WriteFileError!void { + // TODO make `try @errorCast(...)` work + return @errorCast(writeFileAllUnseekableInner(self, in_file, args)); +} + +fn writeFileAllUnseekableInner(self: File, in_file: File, args: WriteFileOptions) anyerror!void { const headers = args.headers_and_trailers[0..args.header_count]; const trailers = args.headers_and_trailers[args.header_count..]; diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index d36cd10aee..1eea06e9c9 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1283,26 +1283,22 @@ pub const basic_authorization = struct { } pub fn valueLengthFromUri(uri: Uri) usize { - var stream = std.io.countingWriter(std.io.null_writer); - try stream.writer().print("{user}", .{uri.user orelse Uri.Component.empty}); - const user_len = stream.bytes_written; - stream.bytes_written = 0; - try stream.writer().print("{password}", .{uri.password orelse Uri.Component.empty}); - const password_len = stream.bytes_written; + // TODO don't abuse formatted printing to count percent encoded characters + const user_len = std.fmt.count("{fuser}", .{uri.user orelse Uri.Component.empty}); + const password_len = std.fmt.count("{fpassword}", .{uri.password orelse Uri.Component.empty}); return valueLength(@intCast(user_len), @intCast(password_len)); } pub fn value(uri: Uri, out: []u8) []u8 { var buf: [max_user_len + ":".len + max_password_len]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - stream.writer().print("{user}", .{uri.user orelse Uri.Component.empty}) catch - unreachable; - assert(stream.pos <= max_user_len); - stream.writer().print(":{password}", .{uri.password orelse Uri.Component.empty}) catch - unreachable; - + var bw: std.io.BufferedWriter = undefined; + bw.initFixed(&buf); + bw.print("{fuser}:{fpassword}", .{ + uri.user orelse Uri.Component.empty, + uri.password orelse Uri.Component.empty, + }) catch unreachable; @memcpy(out[0..prefix.len], prefix); - const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], stream.getWritten()); + const base64 = std.base64.standard.Encoder.encode(out[prefix.len..], bw.getWritten()); return out[0 .. prefix.len + base64.len]; } }; diff --git a/lib/std/io/AllocatingWriter.zig b/lib/std/io/AllocatingWriter.zig index 8592814099..32280d9fd9 100644 --- a/lib/std/io/AllocatingWriter.zig +++ b/lib/std/io/AllocatingWriter.zig @@ -1,4 +1,3 @@ -//! TODO rename to AllocatingWriter. //! While it is possible to use `std.ArrayList` as the underlying writer when //! using `std.io.BufferedWriter` by populating the `std.io.Writer` interface //! and then using an empty buffer, it means that every use of @@ -41,6 +40,12 @@ pub fn init(aw: *AllocatingWriter, allocator: std.mem.Allocator) *std.io.Buffere return &aw.buffered_writer; } +pub fn deinit(aw: *AllocatingWriter) void { + const written = aw.written; + aw.allocator.free(written.ptr[0 .. written.len + aw.buffered_writer.buffer.len]); + aw.* = undefined; +} + /// Replaces `array_list` with empty, taking ownership of the memory. pub fn fromArrayList( aw: *AllocatingWriter, @@ -184,3 +189,15 @@ fn writeFile( for (trailers) |bytes| list.appendSliceAssumeCapacity(bytes); return list.items.len - start_len; } + +test AllocatingWriter { + var aw: AllocatingWriter = undefined; + const bw = aw.init(std.testing.allocator); + defer aw.deinit(); + + const x: i32 = 42; + const y: i32 = 1234; + try bw.print("x: {}\ny: {}\n", .{ x, y }); + + try std.testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", aw.getWritten()); +} diff --git a/lib/std/io/CountingWriter.zig b/lib/std/io/CountingWriter.zig index b37d066bcd..21e0ac132c 100644 --- a/lib/std/io/CountingWriter.zig +++ b/lib/std/io/CountingWriter.zig @@ -1,3 +1,6 @@ +//! TODO make this more like AllocatingWriter, managing the state of +//! BufferedWriter both as the output and the input, but with only +//! one buffer. const std = @import("../std.zig"); const CountingWriter = @This(); const assert = std.debug.assert; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index f52135f237..99013ceba3 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -390,8 +390,9 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)]; const actual_truncated = window_start + actual_window.len < actual.len; - const stderr = std.io.getStdErr(); - const ttyconf = std.io.tty.detectConfig(stderr); + var bw = std.debug.lockStdErr2(); + defer std.debug.unlockStdErr(); + const ttyconf = std.io.tty.detectConfig(std.io.getStdErr()); var differ = if (T == u8) BytesDiffer{ .expected = expected_window, .actual = actual_window, @@ -415,7 +416,7 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const print("... truncated ...\n", .{}); } } - differ.write(stderr.writer()) catch {}; + differ.write(&bw) catch {}; if (expected_truncated) { const end_offset = window_start + expected_window.len; const num_missing_items = expected.len - (window_start + expected_window.len); @@ -437,7 +438,7 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const print("... truncated ...\n", .{}); } } - differ.write(stderr.writer()) catch {}; + differ.write(&bw) catch {}; if (actual_truncated) { const end_offset = window_start + actual_window.len; const num_missing_items = actual.len - (window_start + actual_window.len); @@ -461,17 +462,17 @@ fn SliceDiffer(comptime T: type) type { const Self = @This(); - pub fn write(self: Self, writer: anytype) !void { + pub fn write(self: Self, bw: *std.io.BufferedWriter) !void { for (self.expected, 0..) |value, i| { const full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; - if (diff) try self.ttyconf.setColor(writer, .red); + if (diff) try self.ttyconf.setColor(bw, .red); if (@typeInfo(T) == .pointer) { - try writer.print("[{}]{*}: {any}\n", .{ full_index, value, value }); + try bw.print("[{}]{*}: {any}\n", .{ full_index, value, value }); } else { - try writer.print("[{}]: {any}\n", .{ full_index, value }); + try bw.print("[{}]: {any}\n", .{ full_index, value }); } - if (diff) try self.ttyconf.setColor(writer, .reset); + if (diff) try self.ttyconf.setColor(bw, .reset); } } }; @@ -482,7 +483,7 @@ const BytesDiffer = struct { actual: []const u8, ttyconf: std.io.tty.Config, - pub fn write(self: BytesDiffer, writer: anytype) !void { + pub fn write(self: BytesDiffer, bw: *std.io.BufferedWriter) !void { var expected_iterator = std.mem.window(u8, self.expected, 16, 16); var row: usize = 0; while (expected_iterator.next()) |chunk| { @@ -492,23 +493,23 @@ const BytesDiffer = struct { const absolute_byte_index = col + row * 16; const diff = if (absolute_byte_index < self.actual.len) self.actual[absolute_byte_index] != byte else true; if (diff) diffs.set(col); - try self.writeDiff(writer, "{X:0>2} ", .{byte}, diff); - if (col == 7) try writer.writeByte(' '); + try self.writeDiff(bw, "{X:0>2} ", .{byte}, diff); + if (col == 7) try bw.writeByte(' '); } - try writer.writeByte(' '); + try bw.writeByte(' '); if (chunk.len < 16) { var missing_columns = (16 - chunk.len) * 3; if (chunk.len < 8) missing_columns += 1; - try writer.writeByteNTimes(' ', missing_columns); + try bw.splatByteAll(' ', missing_columns); } for (chunk, 0..) |byte, col| { const diff = diffs.isSet(col); if (std.ascii.isPrint(byte)) { - try self.writeDiff(writer, "{c}", .{byte}, diff); + try self.writeDiff(bw, "{c}", .{byte}, diff); } else { // TODO: remove this `if` when https://github.com/ziglang/zig/issues/7600 is fixed if (self.ttyconf == .windows_api) { - try self.writeDiff(writer, ".", .{}, diff); + try self.writeDiff(bw, ".", .{}, diff); continue; } @@ -516,22 +517,22 @@ const BytesDiffer = struct { // We don't want to do this for all control codes because most control codes apart from // the ones that Zig has escape sequences for are likely not very useful to print as symbols. switch (byte) { - '\n' => try self.writeDiff(writer, "␊", .{}, diff), - '\r' => try self.writeDiff(writer, "␍", .{}, diff), - '\t' => try self.writeDiff(writer, "␉", .{}, diff), - else => try self.writeDiff(writer, ".", .{}, diff), + '\n' => try self.writeDiff(bw, "␊", .{}, diff), + '\r' => try self.writeDiff(bw, "␍", .{}, diff), + '\t' => try self.writeDiff(bw, "␉", .{}, diff), + else => try self.writeDiff(bw, ".", .{}, diff), } } } - try writer.writeByte('\n'); + try bw.writeByte('\n'); row += 1; } } - fn writeDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, args: anytype, diff: bool) !void { - if (diff) try self.ttyconf.setColor(writer, .red); - try writer.print(fmt, args); - if (diff) try self.ttyconf.setColor(writer, .reset); + fn writeDiff(self: BytesDiffer, bw: *std.io.BufferedWriter, comptime fmt: []const u8, args: anytype, diff: bool) !void { + if (diff) try self.ttyconf.setColor(bw, .red); + try bw.print(fmt, args); + if (diff) try self.ttyconf.setColor(bw, .reset); } }; diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index 503f9a3e39..c55bf3acff 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -159,21 +159,26 @@ pub const RenderOptions = struct { pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void { std.debug.lockStdErr(); defer std.debug.unlockStdErr(); - const stderr = std.io.getStdErr(); - return renderToWriter(eb, options, stderr.writer()) catch return; + var buffer: [256]u8 = undefined; + var bw: std.io.BufferedWriter = .{ + .unbuffered_writer = std.io.getStdErr().writer(), + .buffer = &buffer, + }; + renderToWriter(eb, options, &bw) catch return; + bw.flush() catch return; } -pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void { +pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, bw: *std.io.BufferedWriter) anyerror!void { if (eb.extra.len == 0) return; for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .red, 0); + try renderErrorMessageToWriter(eb, options, err_msg, bw, "error", .red, 0); } if (options.include_log_text) { const log_text = eb.getCompileLogOutput(); if (log_text.len != 0) { - try writer.writeAll("\nCompile Log Output:\n"); - try writer.writeAll(log_text); + try bw.writeAll("\nCompile Log Output:\n"); + try bw.writeAll(log_text); } } } @@ -182,74 +187,74 @@ fn renderErrorMessageToWriter( eb: ErrorBundle, options: RenderOptions, err_msg_index: MessageIndex, - stderr: anytype, + bw: *std.io.BufferedWriter, kind: []const u8, color: std.io.tty.Color, indent: usize, ) anyerror!void { const ttyconf = options.ttyconf; - var counting_writer = std.io.countingWriter(stderr); - const counting_stderr = counting_writer.writer(); + var counting_writer: std.io.CountingWriter = .{ .child_writer = bw.writer() }; + const counting_bw = counting_writer.unbufferedWriter(); const err_msg = eb.getErrorMessage(err_msg_index); if (err_msg.src_loc != .none) { const src = eb.extraData(SourceLocation, @intFromEnum(err_msg.src_loc)); - try counting_stderr.writeByteNTimes(' ', indent); - try ttyconf.setColor(stderr, .bold); - try counting_stderr.print("{s}:{d}:{d}: ", .{ + try counting_bw.writeByteNTimes(' ', indent); + try ttyconf.setColor(bw, .bold); + try counting_bw.print("{s}:{d}:{d}: ", .{ eb.nullTerminatedString(src.data.src_path), src.data.line + 1, src.data.column + 1, }); - try ttyconf.setColor(stderr, color); - try counting_stderr.writeAll(kind); - try counting_stderr.writeAll(": "); + try ttyconf.setColor(bw, color); + try counting_bw.writeAll(kind); + try counting_bw.writeAll(": "); // This is the length of the part before the error message: // e.g. "file.zig:4:5: error: " - const prefix_len: usize = @intCast(counting_stderr.context.bytes_written); - try ttyconf.setColor(stderr, .reset); - try ttyconf.setColor(stderr, .bold); + const prefix_len: usize = @intCast(counting_bw.context.bytes_written); + try ttyconf.setColor(bw, .reset); + try ttyconf.setColor(bw, .bold); if (err_msg.count == 1) { - try writeMsg(eb, err_msg, stderr, prefix_len); - try stderr.writeByte('\n'); + try writeMsg(eb, err_msg, bw, prefix_len); + try bw.writeByte('\n'); } else { - try writeMsg(eb, err_msg, stderr, prefix_len); - try ttyconf.setColor(stderr, .dim); - try stderr.print(" ({d} times)\n", .{err_msg.count}); + try writeMsg(eb, err_msg, bw, prefix_len); + try ttyconf.setColor(bw, .dim); + try bw.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(bw, .reset); if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { - '\t' => try stderr.writeByte(' '), - else => try stderr.writeByte(b), + '\t' => try bw.writeByte(' '), + else => try bw.writeByte(b), }; - try stderr.writeByte('\n'); + try bw.writeByte('\n'); // TODO basic unicode code point monospace width const before_caret = src.data.span_main - src.data.span_start; // -1 since span.main includes the caret const after_caret = src.data.span_end -| src.data.span_main -| 1; - try stderr.writeByteNTimes(' ', src.data.column - before_caret); - try ttyconf.setColor(stderr, .green); - try stderr.writeByteNTimes('~', before_caret); - try stderr.writeByte('^'); - try stderr.writeByteNTimes('~', after_caret); - try stderr.writeByte('\n'); - try ttyconf.setColor(stderr, .reset); + try bw.writeByteNTimes(' ', src.data.column - before_caret); + try ttyconf.setColor(bw, .green); + try bw.writeByteNTimes('~', before_caret); + try bw.writeByte('^'); + try bw.writeByteNTimes('~', after_caret); + try bw.writeByte('\n'); + try ttyconf.setColor(bw, .reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent); + try renderErrorMessageToWriter(eb, options, note, bw, "note", .cyan, indent); } if (src.data.reference_trace_len > 0 and options.include_reference_trace) { - try ttyconf.setColor(stderr, .reset); - try ttyconf.setColor(stderr, .dim); - try stderr.print("referenced by:\n", .{}); + try ttyconf.setColor(bw, .reset); + try ttyconf.setColor(bw, .dim); + try bw.print("referenced by:\n", .{}); var ref_index = src.end; for (0..src.data.reference_trace_len) |_| { const ref_trace = eb.extraData(ReferenceTrace, ref_index); ref_index = ref_trace.end; if (ref_trace.data.src_loc != .none) { const ref_src = eb.getSourceLocation(ref_trace.data.src_loc); - try stderr.print(" {s}: {s}:{d}:{d}\n", .{ + try bw.print(" {s}: {s}:{d}:{d}\n", .{ eb.nullTerminatedString(ref_trace.data.decl_name), eb.nullTerminatedString(ref_src.src_path), ref_src.line + 1, @@ -257,36 +262,36 @@ fn renderErrorMessageToWriter( }); } else if (ref_trace.data.decl_name != 0) { const count = ref_trace.data.decl_name; - try stderr.print( + try bw.print( " {d} reference(s) hidden; use '-freference-trace={d}' to see all references\n", .{ count, count + src.data.reference_trace_len - 1 }, ); } else { - try stderr.print( + try bw.print( " remaining reference traces hidden; use '-freference-trace' to see all reference traces\n", .{}, ); } } - try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(bw, .reset); } } else { - try ttyconf.setColor(stderr, color); - try stderr.writeByteNTimes(' ', indent); - try stderr.writeAll(kind); - try stderr.writeAll(": "); - try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(bw, color); + try bw.writeByteNTimes(' ', indent); + try bw.writeAll(kind); + try bw.writeAll(": "); + try ttyconf.setColor(bw, .reset); const msg = eb.nullTerminatedString(err_msg.msg); if (err_msg.count == 1) { - try stderr.print("{s}\n", .{msg}); + try bw.print("{s}\n", .{msg}); } else { - try stderr.print("{s}", .{msg}); - try ttyconf.setColor(stderr, .dim); - try stderr.print(" ({d} times)\n", .{err_msg.count}); + try bw.print("{s}", .{msg}); + try ttyconf.setColor(bw, .dim); + try bw.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(bw, .reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent + 4); + try renderErrorMessageToWriter(eb, options, note, bw, "note", .cyan, indent + 4); } } } @@ -295,13 +300,13 @@ fn renderErrorMessageToWriter( /// to allow for long, good-looking error messages. /// /// This is used to split the message in `@compileError("hello\nworld")` for example. -fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, stderr: anytype, indent: usize) !void { +fn writeMsg(eb: ErrorBundle, err_msg: ErrorMessage, bw: *std.io.BufferedWriter, indent: usize) !void { var lines = std.mem.splitScalar(u8, eb.nullTerminatedString(err_msg.msg), '\n'); while (lines.next()) |line| { - try stderr.writeAll(line); + try bw.writeAll(line); if (lines.index == null) break; - try stderr.writeByte('\n'); - try stderr.writeByteNTimes(' ', indent); + try bw.writeByte('\n'); + try bw.writeByteNTimes(' ', indent); } } @@ -398,7 +403,7 @@ pub const Wip = struct { pub fn printString(wip: *Wip, comptime fmt: []const u8, args: anytype) Allocator.Error!String { const gpa = wip.gpa; const index: String = @intCast(wip.string_bytes.items.len); - try wip.string_bytes.writer(gpa).print(fmt, args); + try wip.string_bytes.print(gpa, fmt, args); try wip.string_bytes.append(gpa, 0); return index; } @@ -788,9 +793,10 @@ pub const Wip = struct { const ttyconf: std.io.tty.Config = .no_color; - var bundle_buf = std.ArrayList(u8).init(std.testing.allocator); + var bundle_buf: std.io.AllocatingWriter = undefined; + const bundle_bw = bundle_buf.init(std.testing.allocator); defer bundle_buf.deinit(); - try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_buf.writer()); + try bundle.renderToWriter(.{ .ttyconf = ttyconf }, bundle_bw); var copy = copy: { var wip: ErrorBundle.Wip = undefined; @@ -803,10 +809,11 @@ pub const Wip = struct { }; defer copy.deinit(std.testing.allocator); - var copy_buf = std.ArrayList(u8).init(std.testing.allocator); + var copy_buf: std.io.AllocatingWriter = undefined; + const copy_bw = copy_buf.init(std.testing.allocator); defer copy_buf.deinit(); - try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_buf.writer()); + try copy.renderToWriter(.{ .ttyconf = ttyconf }, copy_bw); - try std.testing.expectEqualStrings(bundle_buf.items, copy_buf.items); + try std.testing.expectEqualStrings(bundle_bw.getWritten(), copy_bw.getWritten()); } };