Commit 8d80d67693

Andrew Kelley <andrew@ziglang.org>
2025-08-28 01:05:20
resinator: some updates to avoid GenericWriter
These are some hastily made, untested changes to get things compiling again, since Ryan is working on a better upgrade patchset in the meantime.
1 parent f788496
Changed files (6)
lib/compiler/aro/aro/Compilation.zig
@@ -537,8 +537,15 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi
     var allocating: std.Io.Writer.Allocating = .init(comp.gpa);
     defer allocating.deinit();
 
-    const buf = &allocating.writer;
+    generateBuiltinMacrosWriter(comp, system_defines_mode, &allocating.writer) catch |err| switch (err) {
+        error.WriteFailed => return error.OutOfMemory,
+        else => |e| return e,
+    };
 
+    return comp.addSourceFromBuffer("<builtin>", allocating.written());
+}
+
+pub fn generateBuiltinMacrosWriter(comp: *Compilation, system_defines_mode: SystemDefinesMode, buf: *Writer) !void {
     if (system_defines_mode == .include_system_defines) {
         try buf.writeAll(
             \\#define __VERSION__ "Aro
@@ -576,8 +583,6 @@ pub fn generateBuiltinMacros(comp: *Compilation, system_defines_mode: SystemDefi
     if (system_defines_mode == .include_system_defines) {
         try comp.generateSystemDefines(buf);
     }
-
-    return comp.addSourceFromBuffer("<builtin>", allocating.written());
 }
 
 fn generateFloatMacros(w: *Writer, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void {
lib/compiler/resinator/cli.zig
@@ -520,8 +520,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 // - or / on its own is an error
                 else => {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid option: {s}", .{arg.prefixSlice()});
+                    try err_details.msg.print(allocator, "invalid option: {s}", .{arg.prefixSlice()});
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     continue :next_arg;
@@ -532,8 +531,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
         const args_remaining = args.len - arg_i;
         if (args_remaining <= 2 and arg.looksLikeFilepath()) {
             var err_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i };
-            var msg_writer = err_details.msg.writer(allocator);
-            try msg_writer.writeAll("this argument was inferred to be a filepath, so argument parsing was terminated");
+            try err_details.msg.appendSlice(allocator, "this argument was inferred to be a filepath, so argument parsing was terminated");
             try diagnostics.append(err_details);
 
             break;
@@ -550,16 +548,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":output-format")) {
                 const value = arg.value(":output-format".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":output-format".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":output-format".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
                 };
                 output_format = std.meta.stringToEnum(Options.OutputFormat, value.slice) orelse blk: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid output format setting: {s} ", .{value.slice});
+                    try err_details.msg.print(allocator, "invalid output format setting: {s} ", .{value.slice});
                     try diagnostics.append(err_details);
                     break :blk output_format;
                 };
@@ -569,16 +565,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":auto-includes")) {
                 const value = arg.value(":auto-includes".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":auto-includes".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":auto-includes".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
                 };
                 options.auto_includes = std.meta.stringToEnum(Options.AutoIncludes, value.slice) orelse blk: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid auto includes setting: {s} ", .{value.slice});
+                    try err_details.msg.print(allocator, "invalid auto includes setting: {s} ", .{value.slice});
                     try diagnostics.append(err_details);
                     break :blk options.auto_includes;
                 };
@@ -587,16 +581,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":input-format")) {
                 const value = arg.value(":input-format".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":input-format".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":input-format".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
                 };
                 input_format = std.meta.stringToEnum(Options.InputFormat, value.slice) orelse blk: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid input format setting: {s} ", .{value.slice});
+                    try err_details.msg.print(allocator, "invalid input format setting: {s} ", .{value.slice});
                     try diagnostics.append(err_details);
                     break :blk input_format;
                 };
@@ -606,16 +598,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile-fmt")) {
                 const value = arg.value(":depfile-fmt".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":depfile-fmt".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":depfile-fmt".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
                 };
                 options.depfile_fmt = std.meta.stringToEnum(Options.DepfileFormat, value.slice) orelse blk: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid depfile format setting: {s} ", .{value.slice});
+                    try err_details.msg.print(allocator, "invalid depfile format setting: {s} ", .{value.slice});
                     try diagnostics.append(err_details);
                     break :blk options.depfile_fmt;
                 };
@@ -624,8 +614,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":depfile")) {
                 const value = arg.value(":depfile".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":depfile".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":depfile".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -643,8 +632,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, ":target")) {
                 const value = arg.value(":target".len, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":target".len) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(":target".len) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -655,8 +643,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 const arch_str = target_it.first();
                 const arch = cvtres.supported_targets.Arch.fromStringIgnoreCase(arch_str) orelse {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid or unsupported target architecture: {s}", .{arch_str});
+                    try err_details.msg.print(allocator, "invalid or unsupported target architecture: {s}", .{arch_str});
                     try diagnostics.append(err_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
@@ -680,13 +667,11 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                         .prefix_len = arg.prefixSlice().len,
                         .value_offset = arg.name_offset + 3,
                     } };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value for {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(3) });
+                    try err_details.msg.print(allocator, "missing value for {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(3) });
                     try diagnostics.append(err_details);
                 }
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(3) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(3) });
                 try diagnostics.append(err_details);
                 arg_i += 1;
                 continue :next_arg;
@@ -695,16 +680,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             else if (std.ascii.startsWithIgnoreCase(arg_name, "tn")) {
                 const value = arg.value(2, arg_i, args) catch no_value: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                     try diagnostics.append(err_details);
                     // dummy zero-length slice starting where the value would have been
                     const value_start = arg.name_offset + 2;
                     break :no_value Arg.Value{ .slice = arg.full[value_start..value_start] };
                 };
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                 try diagnostics.append(err_details);
                 arg_i += value.index_increment;
                 continue :next_arg;
@@ -716,16 +699,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             {
                 const value = arg.value(2, arg_i, args) catch no_value: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                     try diagnostics.append(err_details);
                     // dummy zero-length slice starting where the value would have been
                     const value_start = arg.name_offset + 2;
                     break :no_value Arg.Value{ .slice = arg.full[value_start..value_start] };
                 };
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                 try diagnostics.append(err_details);
                 arg_i += value.index_increment;
                 continue :next_arg;
@@ -733,8 +714,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             // Unsupported MUI options that do not need a value
             else if (std.ascii.startsWithIgnoreCase(arg_name, "g1")) {
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionSpan(2) };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                 try diagnostics.append(err_details);
                 arg.name_offset += 2;
             }
@@ -747,15 +727,13 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 std.ascii.startsWithIgnoreCase(arg_name, "ta"))
             {
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionSpan(2) };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                 try diagnostics.append(err_details);
                 arg.name_offset += 2;
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "fo")) {
                 const value = arg.value(2, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing output path after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                    try err_details.msg.print(allocator, "missing output path after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -767,8 +745,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "sl")) {
                 const value = arg.value(2, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing language tag after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                    try err_details.msg.print(allocator, "missing language tag after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -776,24 +753,20 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 const percent_str = value.slice;
                 const percent: u32 = parsePercent(percent_str) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid percent format '{s}'", .{percent_str});
+                    try err_details.msg.print(allocator, "invalid percent format '{s}'", .{percent_str});
                     try diagnostics.append(err_details);
                     var note_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = false, .arg_index = arg_i };
-                    var note_writer = note_details.msg.writer(allocator);
-                    try note_writer.writeAll("string length percent must be an integer between 1 and 100 (inclusive)");
+                    try note_details.msg.appendSlice(allocator, "string length percent must be an integer between 1 and 100 (inclusive)");
                     try diagnostics.append(note_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
                 };
                 if (percent == 0 or percent > 100) {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("percent out of range: {} (parsed from '{s}')", .{ percent, percent_str });
+                    try err_details.msg.print(allocator, "percent out of range: {} (parsed from '{s}')", .{ percent, percent_str });
                     try diagnostics.append(err_details);
                     var note_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = false, .arg_index = arg_i };
-                    var note_writer = note_details.msg.writer(allocator);
-                    try note_writer.writeAll("string length percent must be an integer between 1 and 100 (inclusive)");
+                    try note_details.msg.appendSlice(allocator, "string length percent must be an integer between 1 and 100 (inclusive)");
                     try diagnostics.append(note_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
@@ -805,8 +778,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "ln")) {
                 const value = arg.value(2, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing language tag after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
+                    try err_details.msg.print(allocator, "missing language tag after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(2) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -814,16 +786,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 const tag = value.slice;
                 options.default_language_id = lang.tagToInt(tag) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid language tag: {s}", .{tag});
+                    try err_details.msg.print(allocator, "invalid language tag: {s}", .{tag});
                     try diagnostics.append(err_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
                 };
                 if (options.default_language_id.? == lang.LOCALE_CUSTOM_UNSPECIFIED) {
                     var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("language tag '{s}' does not have an assigned ID so it will be resolved to LOCALE_CUSTOM_UNSPECIFIED (id=0x{x})", .{ tag, lang.LOCALE_CUSTOM_UNSPECIFIED });
+                    try err_details.msg.print(allocator, "language tag '{s}' does not have an assigned ID so it will be resolved to LOCALE_CUSTOM_UNSPECIFIED (id=0x{x})", .{ tag, lang.LOCALE_CUSTOM_UNSPECIFIED });
                     try diagnostics.append(err_details);
                 }
                 arg_i += value.index_increment;
@@ -831,8 +801,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "l")) {
                 const value = arg.value(1, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing language ID after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing language ID after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -840,8 +809,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 const num_str = value.slice;
                 options.default_language_id = lang.parseInt(num_str) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid language ID: {s}", .{num_str});
+                    try err_details.msg.print(allocator, "invalid language ID: {s}", .{num_str});
                     try diagnostics.append(err_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
@@ -860,16 +828,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             {
                 const value = arg.value(1, arg_i, args) catch no_value: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     // dummy zero-length slice starting where the value would have been
                     const value_start = arg.name_offset + 1;
                     break :no_value Arg.Value{ .slice = arg.full[value_start..value_start] };
                 };
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                 try diagnostics.append(err_details);
                 arg_i += value.index_increment;
                 continue :next_arg;
@@ -882,16 +848,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             {
                 const value = arg.value(1, arg_i, args) catch no_value: {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing value after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     // dummy zero-length slice starting where the value would have been
                     const value_start = arg.name_offset + 1;
                     break :no_value Arg.Value{ .slice = arg.full[value_start..value_start] };
                 };
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                 try diagnostics.append(err_details);
                 arg_i += value.index_increment;
                 continue :next_arg;
@@ -899,15 +863,13 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             // 1 char unsupported LCX/LCE options that do not need a value
             else if (std.ascii.startsWithIgnoreCase(arg_name, "t")) {
                 var err_details = Diagnostics.ErrorDetails{ .type = .err, .arg_index = arg_i, .arg_span = arg.optionSpan(1) };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                try err_details.msg.print(allocator, "the {s}{s} option is unsupported", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                 try diagnostics.append(err_details);
                 arg.name_offset += 1;
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "c")) {
                 const value = arg.value(1, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing code page ID after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing code page ID after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -915,8 +877,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 const num_str = value.slice;
                 const code_page_id = std.fmt.parseUnsigned(u16, num_str, 10) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("invalid code page ID: {s}", .{num_str});
+                    try err_details.msg.print(allocator, "invalid code page ID: {s}", .{num_str});
                     try diagnostics.append(err_details);
                     arg_i += value.index_increment;
                     continue :next_arg;
@@ -924,16 +885,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 options.default_code_page = code_pages.getByIdentifierEnsureSupported(code_page_id) catch |err| switch (err) {
                     error.InvalidCodePage => {
                         var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                        var msg_writer = err_details.msg.writer(allocator);
-                        try msg_writer.print("invalid or unknown code page ID: {}", .{code_page_id});
+                        try err_details.msg.print(allocator, "invalid or unknown code page ID: {}", .{code_page_id});
                         try diagnostics.append(err_details);
                         arg_i += value.index_increment;
                         continue :next_arg;
                     },
                     error.UnsupportedCodePage => {
                         var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                        var msg_writer = err_details.msg.writer(allocator);
-                        try msg_writer.print("unsupported code page: {s} (id={})", .{
+                        try err_details.msg.print(allocator, "unsupported code page: {s} (id={})", .{
                             @tagName(code_pages.getByIdentifier(code_page_id) catch unreachable),
                             code_page_id,
                         });
@@ -957,8 +916,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "i")) {
                 const value = arg.value(1, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing include path after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing include path after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -986,15 +944,13 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                 // Undocumented option with unknown function
                 // TODO: More investigation to figure out what it does (if anything)
                 var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = arg_i, .arg_span = arg.optionSpan(1) };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("option {s}{s} has no effect (it is undocumented and its function is unknown in the Win32 RC compiler)", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                try err_details.msg.print(allocator, "option {s}{s} has no effect (it is undocumented and its function is unknown in the Win32 RC compiler)", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                 try diagnostics.append(err_details);
                 arg.name_offset += 1;
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "d")) {
                 const value = arg.value(1, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing symbol to define after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing symbol to define after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -1009,8 +965,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                     try options.define(symbol, symbol_value);
                 } else {
                     var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("symbol \"{s}\" is not a valid identifier and therefore cannot be defined", .{symbol});
+                    try err_details.msg.print(allocator, "symbol \"{s}\" is not a valid identifier and therefore cannot be defined", .{symbol});
                     try diagnostics.append(err_details);
                 }
                 arg_i += value.index_increment;
@@ -1018,8 +973,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
             } else if (std.ascii.startsWithIgnoreCase(arg_name, "u")) {
                 const value = arg.value(1, arg_i, args) catch {
                     var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.missingSpan() };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("missing symbol to undefine after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
+                    try err_details.msg.print(allocator, "missing symbol to undefine after {s}{s} option", .{ arg.prefixSlice(), arg.optionWithoutPrefix(1) });
                     try diagnostics.append(err_details);
                     arg_i += 1;
                     break :next_arg;
@@ -1029,16 +983,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                     try options.undefine(symbol);
                 } else {
                     var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = arg_i, .arg_span = value.argSpan(arg) };
-                    var msg_writer = err_details.msg.writer(allocator);
-                    try msg_writer.print("symbol \"{s}\" is not a valid identifier and therefore cannot be undefined", .{symbol});
+                    try err_details.msg.print(allocator, "symbol \"{s}\" is not a valid identifier and therefore cannot be undefined", .{symbol});
                     try diagnostics.append(err_details);
                 }
                 arg_i += value.index_increment;
                 continue :next_arg;
             } else {
                 var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i, .arg_span = arg.optionAndAfterSpan() };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("invalid option: {s}{s}", .{ arg.prefixSlice(), arg.name() });
+                try err_details.msg.print(allocator, "invalid option: {s}{s}", .{ arg.prefixSlice(), arg.name() });
                 try diagnostics.append(err_details);
                 arg_i += 1;
                 continue :next_arg;
@@ -1055,16 +1007,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
 
     if (positionals.len == 0) {
         var err_details = Diagnostics.ErrorDetails{ .print_args = false, .arg_index = arg_i };
-        var msg_writer = err_details.msg.writer(allocator);
-        try msg_writer.writeAll("missing input filename");
+        try err_details.msg.appendSlice(allocator, "missing input filename");
         try diagnostics.append(err_details);
 
         if (args.len > 0) {
             const last_arg = args[args.len - 1];
             if (arg_i > 0 and last_arg.len > 0 and last_arg[0] == '/' and isSupportedInputExtension(std.fs.path.extension(last_arg))) {
                 var note_details = Diagnostics.ErrorDetails{ .type = .note, .print_args = true, .arg_index = arg_i - 1 };
-                var note_writer = note_details.msg.writer(allocator);
-                try note_writer.writeAll("if this argument was intended to be the input filename, adding -- in front of it will exclude it from option parsing");
+                try note_details.msg.appendSlice(allocator, "if this argument was intended to be the input filename, adding -- in front of it will exclude it from option parsing");
                 try diagnostics.append(note_details);
             }
         }
@@ -1099,16 +1049,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
     if (positionals.len > 1) {
         if (output_filename != null) {
             var err_details = Diagnostics.ErrorDetails{ .arg_index = arg_i + 1 };
-            var msg_writer = err_details.msg.writer(allocator);
-            try msg_writer.writeAll("output filename already specified");
+            try err_details.msg.appendSlice(allocator, "output filename already specified");
             try diagnostics.append(err_details);
             var note_details = Diagnostics.ErrorDetails{
                 .type = .note,
                 .arg_index = output_filename_context.arg.index,
                 .arg_span = output_filename_context.arg.value.argSpan(output_filename_context.arg.arg),
             };
-            var note_writer = note_details.msg.writer(allocator);
-            try note_writer.writeAll("output filename previously specified here");
+            try note_details.msg.appendSlice(allocator, "output filename previously specified here");
             try diagnostics.append(note_details);
         } else {
             output_filename = positionals[1];
@@ -1173,16 +1121,15 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
     var print_output_format_source_note: bool = false;
     if (options.depfile_path != null and (options.input_format == .res or options.output_format == .rcpp)) {
         var err_details = Diagnostics.ErrorDetails{ .type = .warning, .arg_index = depfile_context.index, .arg_span = depfile_context.value.argSpan(depfile_context.arg) };
-        var msg_writer = err_details.msg.writer(allocator);
         if (options.input_format == .res) {
-            try msg_writer.print("the {s}{s} option was ignored because the input format is '{s}'", .{
+            try err_details.msg.print(allocator, "the {s}{s} option was ignored because the input format is '{s}'", .{
                 depfile_context.arg.prefixSlice(),
                 depfile_context.arg.optionWithoutPrefix(depfile_context.option_len),
                 @tagName(options.input_format),
             });
             print_input_format_source_note = true;
         } else if (options.output_format == .rcpp) {
-            try msg_writer.print("the {s}{s} option was ignored because the output format is '{s}'", .{
+            try err_details.msg.print(allocator, "the {s}{s} option was ignored because the output format is '{s}'", .{
                 depfile_context.arg.prefixSlice(),
                 depfile_context.arg.optionWithoutPrefix(depfile_context.option_len),
                 @tagName(options.output_format),
@@ -1193,16 +1140,14 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
     }
     if (!isSupportedTransformation(options.input_format, options.output_format)) {
         var err_details = Diagnostics.ErrorDetails{ .arg_index = input_filename_arg_i, .print_args = false };
-        var msg_writer = err_details.msg.writer(allocator);
-        try msg_writer.print("input format '{s}' cannot be converted to output format '{s}'", .{ @tagName(options.input_format), @tagName(options.output_format) });
+        try err_details.msg.print(allocator, "input format '{s}' cannot be converted to output format '{s}'", .{ @tagName(options.input_format), @tagName(options.output_format) });
         try diagnostics.append(err_details);
         print_input_format_source_note = true;
         print_output_format_source_note = true;
     }
     if (options.preprocess == .only and options.output_format != .rcpp) {
         var err_details = Diagnostics.ErrorDetails{ .arg_index = preprocess_only_context.index };
-        var msg_writer = err_details.msg.writer(allocator);
-        try msg_writer.print("the {s}{s} option cannot be used with output format '{s}'", .{
+        try err_details.msg.print(allocator, "the {s}{s} option cannot be used with output format '{s}'", .{
             preprocess_only_context.arg.prefixSlice(),
             preprocess_only_context.arg.optionWithoutPrefix(preprocess_only_context.option_len),
             @tagName(options.output_format),
@@ -1214,8 +1159,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
         switch (input_format_source) {
             .inferred_from_input_filename => {
                 var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = input_filename_arg_i };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.writeAll("the input format was inferred from the input filename");
+                try err_details.msg.appendSlice(allocator, "the input format was inferred from the input filename");
                 try diagnostics.append(err_details);
             },
             .input_format_arg => {
@@ -1224,8 +1168,7 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                     .arg_index = input_format_context.index,
                     .arg_span = input_format_context.value.argSpan(input_format_context.arg),
                 };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.writeAll("the input format was specified here");
+                try err_details.msg.appendSlice(allocator, "the input format was specified here");
                 try diagnostics.append(err_details);
             },
         }
@@ -1234,11 +1177,10 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
         switch (output_format_source) {
             .inferred_from_input_filename, .unable_to_infer_from_input_filename => {
                 var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = input_filename_arg_i };
-                var msg_writer = err_details.msg.writer(allocator);
                 if (output_format_source == .inferred_from_input_filename) {
-                    try msg_writer.writeAll("the output format was inferred from the input filename");
+                    try err_details.msg.appendSlice(allocator, "the output format was inferred from the input filename");
                 } else {
-                    try msg_writer.writeAll("the output format was unable to be inferred from the input filename, so the default was used");
+                    try err_details.msg.appendSlice(allocator, "the output format was unable to be inferred from the input filename, so the default was used");
                 }
                 try diagnostics.append(err_details);
             },
@@ -1248,11 +1190,10 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                     .arg => |ctx| .{ .type = .note, .arg_index = ctx.index, .arg_span = ctx.value.argSpan(ctx.arg) },
                     .unspecified => unreachable,
                 };
-                var msg_writer = err_details.msg.writer(allocator);
                 if (output_format_source == .inferred_from_output_filename) {
-                    try msg_writer.writeAll("the output format was inferred from the output filename");
+                    try err_details.msg.appendSlice(allocator, "the output format was inferred from the output filename");
                 } else {
-                    try msg_writer.writeAll("the output format was unable to be inferred from the output filename, so the default was used");
+                    try err_details.msg.appendSlice(allocator, "the output format was unable to be inferred from the output filename, so the default was used");
                 }
                 try diagnostics.append(err_details);
             },
@@ -1262,14 +1203,12 @@ pub fn parse(allocator: Allocator, args: []const []const u8, diagnostics: *Diagn
                     .arg_index = output_format_context.index,
                     .arg_span = output_format_context.value.argSpan(output_format_context.arg),
                 };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.writeAll("the output format was specified here");
+                try err_details.msg.appendSlice(allocator, "the output format was specified here");
                 try diagnostics.append(err_details);
             },
             .inferred_from_preprocess_only => {
                 var err_details = Diagnostics.ErrorDetails{ .type = .note, .arg_index = preprocess_only_context.index };
-                var msg_writer = err_details.msg.writer(allocator);
-                try msg_writer.print("the output format was inferred from the usage of the {s}{s} option", .{
+                try err_details.msg.print(allocator, "the output format was inferred from the usage of the {s}{s} option", .{
                     preprocess_only_context.arg.prefixSlice(),
                     preprocess_only_context.arg.optionWithoutPrefix(preprocess_only_context.option_len),
                 });
lib/compiler/resinator/compile.zig
@@ -61,7 +61,7 @@ pub const CompileOptions = struct {
     warn_instead_of_error_on_invalid_code_page: bool = false,
 };
 
-pub fn compile(allocator: Allocator, source: []const u8, writer: anytype, options: CompileOptions) !void {
+pub fn compile(allocator: Allocator, source: []const u8, writer: *std.Io.Writer, options: CompileOptions) !void {
     var lexer = lex.Lexer.init(source, .{
         .default_code_page = options.default_code_page,
         .source_mappings = options.source_mappings,
@@ -194,7 +194,7 @@ pub const Compiler = struct {
         characteristics: u32 = 0,
     };
 
-    pub fn writeRoot(self: *Compiler, root: *Node.Root, writer: anytype) !void {
+    pub fn writeRoot(self: *Compiler, root: *Node.Root, writer: *std.Io.Writer) !void {
         try writeEmptyResource(writer);
         for (root.body) |node| {
             try self.writeNode(node, writer);
@@ -236,7 +236,7 @@ pub const Compiler = struct {
         }
     }
 
-    pub fn writeNode(self: *Compiler, node: *Node, writer: anytype) !void {
+    pub fn writeNode(self: *Compiler, node: *Node, writer: *std.Io.Writer) !void {
         switch (node.id) {
             .root => unreachable, // writeRoot should be called directly instead
             .resource_external => try self.writeResourceExternal(@alignCast(@fieldParentPtr("base", node)), writer),
@@ -479,7 +479,7 @@ pub const Compiler = struct {
         return buf.toOwnedSlice();
     }
 
-    pub fn writeResourceExternal(self: *Compiler, node: *Node.ResourceExternal, writer: anytype) !void {
+    pub fn writeResourceExternal(self: *Compiler, node: *Node.ResourceExternal, writer: *std.Io.Writer) !void {
         // Init header with data size zero for now, will need to fill it in later
         var header = try self.resourceHeader(node.id, node.type, .{});
         defer header.deinit(self.allocator);
@@ -1226,33 +1226,31 @@ pub const Compiler = struct {
     }
 
     pub fn writeResourceRawData(self: *Compiler, node: *Node.ResourceRawData, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
         // The header's data length field is a u32 so limit the resource's data size so that
         // we know we can always specify the real size.
-        var limited_writer = limitedWriter(data_buffer.writer(), std.math.maxInt(u32));
-        const data_writer = limited_writer.writer();
+        const data_writer = &data_buffer.writer;
 
         for (node.raw_data) |expression| {
             const data = try self.evaluateDataExpression(expression);
             defer data.deinit(self.allocator);
             data.write(data_writer) catch |err| switch (err) {
-                error.NoSpaceLeft => {
+                error.WriteFailed => {
                     return self.addErrorDetailsAndFail(.{
                         .err = .resource_data_size_exceeds_max,
                         .token = node.id,
                     });
                 },
-                else => |e| return e,
             };
         }
 
         // This intCast can't fail because the limitedWriter above guarantees that
         // we will never write more than maxInt(u32) bytes.
-        const data_len: u32 = @intCast(data_buffer.items.len);
+        const data_len: u32 = @intCast(data_buffer.written().len);
         try self.writeResourceHeader(writer, node.id, node.type, data_len, node.common_resource_attributes, self.state.language);
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_len);
     }
 
@@ -1306,16 +1304,15 @@ pub const Compiler = struct {
     }
 
     pub fn writeAccelerators(self: *Compiler, node: *Node.Accelerators, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
 
         // The header's data length field is a u32 so limit the resource's data size so that
         // we know we can always specify the real size.
-        var limited_writer = limitedWriter(data_buffer.writer(), std.math.maxInt(u32));
-        const data_writer = limited_writer.writer();
+        const data_writer = &data_buffer.writer;
 
         self.writeAcceleratorsData(node, data_writer) catch |err| switch (err) {
-            error.NoSpaceLeft => {
+            error.WriteFailed => {
                 return self.addErrorDetailsAndFail(.{
                     .err = .resource_data_size_exceeds_max,
                     .token = node.id,
@@ -1326,7 +1323,7 @@ pub const Compiler = struct {
 
         // This intCast can't fail because the limitedWriter above guarantees that
         // we will never write more than maxInt(u32) bytes.
-        const data_size: u32 = @intCast(data_buffer.items.len);
+        const data_size: u32 = @intCast(data_buffer.written().len);
         var header = try self.resourceHeader(node.id, node.type, .{
             .data_size = data_size,
         });
@@ -1337,7 +1334,7 @@ pub const Compiler = struct {
 
         try header.write(writer, self.errContext(node.id));
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_size);
     }
 
@@ -1405,12 +1402,11 @@ pub const Compiler = struct {
     };
 
     pub fn writeDialog(self: *Compiler, node: *Node.Dialog, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
         // The header's data length field is a u32 so limit the resource's data size so that
         // we know we can always specify the real size.
-        var limited_writer = limitedWriter(data_buffer.writer(), std.math.maxInt(u32));
-        const data_writer = limited_writer.writer();
+        const data_writer = &data_buffer.writer;
 
         const resource = ResourceType.fromString(.{
             .slice = node.type.slice(self.source),
@@ -1683,7 +1679,7 @@ pub const Compiler = struct {
         ) catch |err| switch (err) {
             // Dialog header and menu/class/title strings can never exceed u32 bytes
             // on their own, so this error is unreachable.
-            error.NoSpaceLeft => unreachable,
+            error.WriteFailed => unreachable,
             else => |e| return e,
         };
 
@@ -1700,10 +1696,10 @@ pub const Compiler = struct {
                 data_writer,
                 resource,
                 // We know the data_buffer len is limited to u32 max.
-                @intCast(data_buffer.items.len),
+                @intCast(data_buffer.written().len),
                 &controls_by_id,
             ) catch |err| switch (err) {
-                error.NoSpaceLeft => {
+                error.WriteFailed => {
                     try self.addErrorDetails(.{
                         .err = .resource_data_size_exceeds_max,
                         .token = node.id,
@@ -1719,7 +1715,7 @@ pub const Compiler = struct {
         }
 
         // We know the data_buffer len is limited to u32 max.
-        const data_size: u32 = @intCast(data_buffer.items.len);
+        const data_size: u32 = @intCast(data_buffer.written().len);
         var header = try self.resourceHeader(node.id, node.type, .{
             .data_size = data_size,
         });
@@ -1730,7 +1726,7 @@ pub const Compiler = struct {
 
         try header.write(writer, self.errContext(node.id));
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_size);
     }
 
@@ -1821,7 +1817,7 @@ pub const Compiler = struct {
                 .token = control.type,
             });
         }
-        try data_writer.writeByteNTimes(0, num_padding);
+        try data_writer.splatByteAll(0, num_padding);
 
         const style = if (control.style) |style_expression|
             // Certain styles are implied by the control type
@@ -1973,16 +1969,15 @@ pub const Compiler = struct {
             try NameOrOrdinal.writeEmpty(data_writer);
         }
 
-        var extra_data_buf = std.array_list.Managed(u8).init(self.allocator);
+        var extra_data_buf: std.Io.Writer.Allocating = .init(self.allocator);
         defer extra_data_buf.deinit();
         // The extra data byte length must be able to fit within a u16.
-        var limited_extra_data_writer = limitedWriter(extra_data_buf.writer(), std.math.maxInt(u16));
-        const extra_data_writer = limited_extra_data_writer.writer();
+        const extra_data_writer = &extra_data_buf.writer;
         for (control.extra_data) |data_expression| {
             const data = try self.evaluateDataExpression(data_expression);
             defer data.deinit(self.allocator);
             data.write(extra_data_writer) catch |err| switch (err) {
-                error.NoSpaceLeft => {
+                error.WriteFailed => {
                     try self.addErrorDetails(.{
                         .err = .control_extra_data_size_exceeds_max,
                         .token = control.type,
@@ -1998,15 +1993,15 @@ pub const Compiler = struct {
             };
         }
         // We know the extra_data_buf size fits within a u16.
-        const extra_data_size: u16 = @intCast(extra_data_buf.items.len);
+        const extra_data_size: u16 = @intCast(extra_data_buf.written().len);
         try data_writer.writeInt(u16, extra_data_size, .little);
-        try data_writer.writeAll(extra_data_buf.items);
+        try data_writer.writeAll(extra_data_buf.written());
     }
 
     pub fn writeToolbar(self: *Compiler, node: *Node.Toolbar, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
-        const data_writer = data_buffer.writer();
+        const data_writer = &data_buffer.writer;
 
         const button_width = evaluateNumberExpression(node.button_width, self.source, self.input_code_pages);
         const button_height = evaluateNumberExpression(node.button_height, self.source, self.input_code_pages);
@@ -2034,7 +2029,7 @@ pub const Compiler = struct {
             }
         }
 
-        const data_size: u32 = @intCast(data_buffer.items.len);
+        const data_size: u32 = @intCast(data_buffer.written().len);
         var header = try self.resourceHeader(node.id, node.type, .{
             .data_size = data_size,
         });
@@ -2044,7 +2039,7 @@ pub const Compiler = struct {
 
         try header.write(writer, self.errContext(node.id));
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_size);
     }
 
@@ -2082,12 +2077,11 @@ pub const Compiler = struct {
     }
 
     pub fn writeMenu(self: *Compiler, node: *Node.Menu, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
         // The header's data length field is a u32 so limit the resource's data size so that
         // we know we can always specify the real size.
-        var limited_writer = limitedWriter(data_buffer.writer(), std.math.maxInt(u32));
-        const data_writer = limited_writer.writer();
+        const data_writer = &data_buffer.writer;
 
         const type_bytes = SourceBytes{
             .slice = node.type.slice(self.source),
@@ -2096,9 +2090,7 @@ pub const Compiler = struct {
         const resource = ResourceType.fromString(type_bytes);
         std.debug.assert(resource == .menu or resource == .menuex);
 
-        var adapted = data_writer.adaptToNewApi(&.{});
-
-        self.writeMenuData(node, &adapted.new_interface, resource) catch |err| switch (err) {
+        self.writeMenuData(node, data_writer, resource) catch |err| switch (err) {
             error.WriteFailed => {
                 return self.addErrorDetailsAndFail(.{
                     .err = .resource_data_size_exceeds_max,
@@ -2110,7 +2102,7 @@ pub const Compiler = struct {
 
         // This intCast can't fail because the limitedWriter above guarantees that
         // we will never write more than maxInt(u32) bytes.
-        const data_size: u32 = @intCast(data_buffer.items.len);
+        const data_size: u32 = @intCast(data_buffer.written().len);
         var header = try self.resourceHeader(node.id, node.type, .{
             .data_size = data_size,
         });
@@ -2121,7 +2113,7 @@ pub const Compiler = struct {
 
         try header.write(writer, self.errContext(node.id));
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_size);
     }
 
@@ -2265,12 +2257,11 @@ pub const Compiler = struct {
     }
 
     pub fn writeVersionInfo(self: *Compiler, node: *Node.VersionInfo, writer: anytype) !void {
-        var data_buffer = std.array_list.Managed(u8).init(self.allocator);
+        var data_buffer: std.Io.Writer.Allocating = .init(self.allocator);
         defer data_buffer.deinit();
         // The node's length field (which is inclusive of the length of all of its children) is a u16
         // so limit the node's data size so that we know we can always specify the real size.
-        var limited_writer = limitedWriter(data_buffer.writer(), std.math.maxInt(u16));
-        const data_writer = limited_writer.writer();
+        const data_writer = &data_buffer.writer;
 
         try data_writer.writeInt(u16, 0, .little); // placeholder size
         try data_writer.writeInt(u16, res.FixedFileInfo.byte_len, .little);
@@ -2354,8 +2345,7 @@ pub const Compiler = struct {
         try fixed_file_info.write(data_writer);
 
         for (node.block_statements) |statement| {
-            var adapted = data_writer.adaptToNewApi(&.{});
-            self.writeVersionNode(statement, &adapted.new_interface, &data_buffer) catch |err| switch (err) {
+            self.writeVersionNode(statement, data_writer, &data_buffer) catch |err| switch (err) {
                 error.WriteFailed => {
                     try self.addErrorDetails(.{
                         .err = .version_node_size_exceeds_max,
@@ -2374,9 +2364,9 @@ pub const Compiler = struct {
 
         // We know that data_buffer.items.len is within the limits of a u16, since we
         // limited the writer to maxInt(u16)
-        const data_size: u16 = @intCast(data_buffer.items.len);
+        const data_size: u16 = @intCast(data_buffer.written().len);
         // And now that we know the full size of this node (including its children), set its size
-        std.mem.writeInt(u16, data_buffer.items[0..2], data_size, .little);
+        std.mem.writeInt(u16, data_buffer.written()[0..2], data_size, .little);
 
         var header = try self.resourceHeader(node.id, node.versioninfo, .{
             .data_size = data_size,
@@ -2387,22 +2377,22 @@ pub const Compiler = struct {
 
         try header.write(writer, self.errContext(node.id));
 
-        var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+        var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
         try writeResourceData(writer, &data_fbs, data_size);
     }
 
     /// Expects writer to be a LimitedWriter limited to u16, meaning all writes to
     /// the writer within this function could return error.NoSpaceLeft, and that buf.items.len
     /// will never be able to exceed maxInt(u16).
-    pub fn writeVersionNode(self: *Compiler, node: *Node, writer: *std.Io.Writer, buf: *std.array_list.Managed(u8)) !void {
+    pub fn writeVersionNode(self: *Compiler, node: *Node, writer: *std.Io.Writer, buf: *std.Io.Writer.Allocating) !void {
         // We can assume that buf.items.len will never be able to exceed the limits of a u16
-        try writeDataPadding(writer, @as(u16, @intCast(buf.items.len)));
+        try writeDataPadding(writer, @as(u16, @intCast(buf.written().len)));
 
-        const node_and_children_size_offset = buf.items.len;
+        const node_and_children_size_offset = buf.written().len;
         try writer.writeInt(u16, 0, .little); // placeholder for size
-        const data_size_offset = buf.items.len;
+        const data_size_offset = buf.written().len;
         try writer.writeInt(u16, 0, .little); // placeholder for data size
-        const data_type_offset = buf.items.len;
+        const data_type_offset = buf.written().len;
         // Data type is string unless the node contains values that are numbers.
         try writer.writeInt(u16, res.VersionNode.type_string, .little);
 
@@ -2432,7 +2422,7 @@ pub const Compiler = struct {
                 // during parsing, so we can just do the correct thing here.
                 var values_size: usize = 0;
 
-                try writeDataPadding(writer, @intCast(buf.items.len));
+                try writeDataPadding(writer, @intCast(buf.written().len));
 
                 for (block_or_value.values, 0..) |value_value_node_uncasted, i| {
                     const value_value_node = value_value_node_uncasted.cast(.block_value_value).?;
@@ -2471,11 +2461,11 @@ pub const Compiler = struct {
                         }
                     }
                 }
-                var data_size_slice = buf.items[data_size_offset..];
+                var data_size_slice = buf.written()[data_size_offset..];
                 std.mem.writeInt(u16, data_size_slice[0..@sizeOf(u16)], @as(u16, @intCast(values_size)), .little);
 
                 if (has_number_value) {
-                    const data_type_slice = buf.items[data_type_offset..];
+                    const data_type_slice = buf.written()[data_type_offset..];
                     std.mem.writeInt(u16, data_type_slice[0..@sizeOf(u16)], res.VersionNode.type_binary, .little);
                 }
 
@@ -2489,8 +2479,8 @@ pub const Compiler = struct {
             else => unreachable,
         }
 
-        const node_and_children_size = buf.items.len - node_and_children_size_offset;
-        const node_and_children_size_slice = buf.items[node_and_children_size_offset..];
+        const node_and_children_size = buf.written().len - node_and_children_size_offset;
+        const node_and_children_size_slice = buf.written()[node_and_children_size_offset..];
         std.mem.writeInt(u16, node_and_children_size_slice[0..@sizeOf(u16)], @as(u16, @intCast(node_and_children_size)), .little);
     }
 
@@ -2973,54 +2963,6 @@ pub fn headerSlurpingReader(comptime size: usize, reader: anytype) HeaderSlurpin
     return .{ .child_reader = reader };
 }
 
-/// Sort of like std.io.LimitedReader, but a Writer.
-/// Returns an error if writing the requested number of bytes
-/// would ever exceed bytes_left, i.e. it does not always
-/// write up to the limit and instead will error if the
-/// limit would be breached if the entire slice was written.
-pub fn LimitedWriter(comptime WriterType: type) type {
-    return struct {
-        inner_writer: WriterType,
-        bytes_left: u64,
-
-        pub const Error = error{NoSpaceLeft} || WriterType.Error;
-        pub const Writer = std.io.GenericWriter(*Self, Error, write);
-
-        const Self = @This();
-
-        pub fn write(self: *Self, bytes: []const u8) Error!usize {
-            if (bytes.len > self.bytes_left) return error.NoSpaceLeft;
-            const amt = try self.inner_writer.write(bytes);
-            self.bytes_left -= amt;
-            return amt;
-        }
-
-        pub fn writer(self: *Self) Writer {
-            return .{ .context = self };
-        }
-    };
-}
-
-/// Returns an initialised `LimitedWriter`
-/// `bytes_left` is a `u64` to be able to take 64 bit file offsets
-pub fn limitedWriter(inner_writer: anytype, bytes_left: u64) LimitedWriter(@TypeOf(inner_writer)) {
-    return .{ .inner_writer = inner_writer, .bytes_left = bytes_left };
-}
-
-test "limitedWriter basic usage" {
-    var buf: [4]u8 = undefined;
-    var fbs = std.io.fixedBufferStream(&buf);
-    var limited_stream = limitedWriter(fbs.writer(), 4);
-    var writer = limited_stream.writer();
-
-    try std.testing.expectEqual(@as(usize, 3), try writer.write("123"));
-    try std.testing.expectEqualSlices(u8, "123", buf[0..3]);
-    try std.testing.expectError(error.NoSpaceLeft, writer.write("45"));
-    try std.testing.expectEqual(@as(usize, 1), try writer.write("4"));
-    try std.testing.expectEqualSlices(u8, "1234", buf[0..4]);
-    try std.testing.expectError(error.NoSpaceLeft, writer.write("5"));
-}
-
 pub const FontDir = struct {
     fonts: std.ArrayListUnmanaged(Font) = .empty,
     /// To keep track of which ids are set and where they were set from
@@ -3246,9 +3188,9 @@ pub const StringTable = struct {
         }
 
         pub fn writeResData(self: *Block, compiler: *Compiler, language: res.Language, block_id: u16, writer: anytype) !void {
-            var data_buffer = std.array_list.Managed(u8).init(compiler.allocator);
+            var data_buffer: std.Io.Writer.Allocating = .init(compiler.allocator);
             defer data_buffer.deinit();
-            const data_writer = data_buffer.writer();
+            const data_writer = &data_buffer.writer;
 
             var i: u8 = 0;
             var string_i: u8 = 0;
@@ -3307,7 +3249,7 @@ pub const StringTable = struct {
             //   16 * (131,070 + 2) = 2,097,152 which is well within the u32 max.
             //
             // Note: The string literal maximum length is enforced by the lexer.
-            const data_size: u32 = @intCast(data_buffer.items.len);
+            const data_size: u32 = @intCast(data_buffer.written().len);
 
             const header = Compiler.ResourceHeader{
                 .name_value = .{ .ordinal = block_id },
@@ -3322,7 +3264,7 @@ pub const StringTable = struct {
             // we fully control and know are numbers, so they have a fixed size.
             try header.writeAssertNoOverflow(writer);
 
-            var data_fbs: std.Io.Reader = .fixed(data_buffer.items);
+            var data_fbs: std.Io.Reader = .fixed(data_buffer.written());
             try Compiler.writeResourceData(writer, &data_fbs, data_size);
         }
     };
lib/compiler/resinator/errors.zig
@@ -1102,11 +1102,10 @@ const CorrespondingLines = struct {
         corresponding_lines.buffered_reader = corresponding_lines.file.reader(&.{});
         errdefer corresponding_lines.deinit();
 
-        var fbs = std.io.fixedBufferStream(&corresponding_lines.line_buf);
-        const writer = fbs.writer();
+        var writer: std.Io.Writer = .fixed(&corresponding_lines.line_buf);
 
         try corresponding_lines.writeLineFromStreamVerbatim(
-            writer,
+            &writer,
             corresponding_lines.buffered_reader.interface.adaptToOldInterface(),
             corresponding_span.start_line,
         );
@@ -1145,11 +1144,10 @@ const CorrespondingLines = struct {
         self.line_len = 0;
         self.visual_line_len = 0;
 
-        var fbs = std.io.fixedBufferStream(&self.line_buf);
-        const writer = fbs.writer();
+        var writer: std.Io.Writer = .fixed(&self.line_buf);
 
         try self.writeLineFromStreamVerbatim(
-            writer,
+            &writer,
             self.buffered_reader.interface.adaptToOldInterface(),
             self.line_num,
         );
@@ -1164,7 +1162,7 @@ const CorrespondingLines = struct {
         return visual_line;
     }
 
-    fn writeLineFromStreamVerbatim(self: *CorrespondingLines, writer: anytype, input: anytype, line_num: usize) !void {
+    fn writeLineFromStreamVerbatim(self: *CorrespondingLines, writer: *std.Io.Writer, input: anytype, line_num: usize) !void {
         while (try readByteOrEof(input)) |byte| {
             switch (byte) {
                 '\n', '\r' => {
@@ -1188,7 +1186,7 @@ const CorrespondingLines = struct {
                         if (writer.writeByte(byte)) {
                             self.line_len += 1;
                         } else |err| switch (err) {
-                            error.NoSpaceLeft => {},
+                            error.WriteFailed => {},
                             else => |e| return e,
                         }
                     }
lib/compiler/resinator/main.zig
@@ -43,11 +43,11 @@ pub fn main() !void {
         cli_args = args[3..];
     }
 
-    var stdout_writer2 = std.fs.File.stdout().writer(&stdout_buffer);
+    var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
     var error_handler: ErrorHandler = switch (zig_integration) {
         true => .{
             .server = .{
-                .out = &stdout_writer2.interface,
+                .out = &stdout_writer.interface,
                 .in = undefined, // won't be receiving messages
             },
         },
@@ -83,18 +83,18 @@ pub fn main() !void {
     defer options.deinit();
 
     if (options.print_help_and_exit) {
-        const stdout = std.fs.File.stdout();
-        try cli.writeUsage(stdout.deprecatedWriter(), "zig rc");
+        try cli.writeUsage(&stdout_writer.interface, "zig rc");
+        try stdout_writer.interface.flush();
         return;
     }
 
     // Don't allow verbose when integrating with Zig via stdout
     options.verbose = false;
 
-    const stdout_writer = std.fs.File.stdout().deprecatedWriter();
     if (options.verbose) {
-        try options.dumpVerbose(stdout_writer);
-        try stdout_writer.writeByte('\n');
+        try options.dumpVerbose(&stdout_writer.interface);
+        try stdout_writer.interface.writeByte('\n');
+        try stdout_writer.interface.flush();
     }
 
     var dependencies_list = std.array_list.Managed([]const u8).init(allocator);
@@ -115,7 +115,7 @@ pub fn main() !void {
 
     const full_input = full_input: {
         if (options.input_format == .rc and options.preprocess != .no) {
-            var preprocessed_buf = std.array_list.Managed(u8).init(allocator);
+            var preprocessed_buf: std.Io.Writer.Allocating = .init(allocator);
             errdefer preprocessed_buf.deinit();
 
             // We're going to throw away everything except the final preprocessed output anyway,
@@ -139,14 +139,15 @@ pub fn main() !void {
             });
 
             if (options.verbose) {
-                try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
+                try stdout_writer.interface.writeAll("Preprocessor: arocc (built-in)\n");
                 for (argv.items[0 .. argv.items.len - 1]) |arg| {
-                    try stdout_writer.print("{s} ", .{arg});
+                    try stdout_writer.interface.print("{s} ", .{arg});
                 }
-                try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
+                try stdout_writer.interface.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
+                try stdout_writer.interface.flush();
             }
 
-            preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) {
+            preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies_list) catch |err| switch (err) {
                 error.GeneratedSourceError => {
                     try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp);
                     std.process.exit(1);
@@ -249,8 +250,9 @@ pub fn main() !void {
                 defer diagnostics.deinit();
 
                 var output_buffer: [4096]u8 = undefined;
-                var res_stream_writer = res_stream.source.writer(allocator).adaptToNewApi(&output_buffer);
-                const output_buffered_stream = &res_stream_writer.new_interface;
+                var res_stream_writer = res_stream.source.writer(allocator, &output_buffer);
+                defer res_stream_writer.deinit(&res_stream.source);
+                const output_buffered_stream = res_stream_writer.interface();
 
                 compile(allocator, final_input, output_buffered_stream, .{
                     .cwd = std.fs.cwd(),
@@ -342,10 +344,10 @@ pub fn main() !void {
     defer coff_stream.deinit(allocator);
 
     var coff_output_buffer: [4096]u8 = undefined;
-    var coff_output_buffered_stream = coff_stream.source.writer(allocator).adaptToNewApi(&coff_output_buffer);
+    var coff_output_buffered_stream = coff_stream.source.writer(allocator, &coff_output_buffer);
 
     var cvtres_diagnostics: cvtres.Diagnostics = .{ .none = {} };
-    cvtres.writeCoff(allocator, &coff_output_buffered_stream.new_interface, resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
+    cvtres.writeCoff(allocator, coff_output_buffered_stream.interface(), resources.list.items, options.coff_options, &cvtres_diagnostics) catch |err| {
         switch (err) {
             error.DuplicateResource => {
                 const duplicate_resource = resources.list.items[cvtres_diagnostics.duplicate_resource];
@@ -382,7 +384,7 @@ pub fn main() !void {
         std.process.exit(1);
     };
 
-    try coff_output_buffered_stream.new_interface.flush();
+    try coff_output_buffered_stream.interface().flush();
 }
 
 const IoStream = struct {
@@ -425,7 +427,7 @@ const IoStream = struct {
     pub const Source = union(enum) {
         file: std.fs.File,
         stdio: std.fs.File,
-        memory: std.ArrayListUnmanaged(u8),
+        memory: std.ArrayList(u8),
         /// The source has been closed and any usage of the Source in this state is illegal (except deinit).
         closed: void,
 
@@ -472,26 +474,34 @@ const IoStream = struct {
             };
         }
 
-        pub const WriterContext = struct {
-            self: *Source,
-            allocator: std.mem.Allocator,
-        };
-        pub const WriteError = std.mem.Allocator.Error || std.fs.File.WriteError;
-        pub const Writer = std.io.GenericWriter(WriterContext, WriteError, write);
-
-        pub fn write(ctx: WriterContext, bytes: []const u8) WriteError!usize {
-            switch (ctx.self.*) {
-                inline .file, .stdio => |file| return file.write(bytes),
-                .memory => |*list| {
-                    try list.appendSlice(ctx.allocator, bytes);
-                    return bytes.len;
-                },
-                .closed => unreachable,
+        pub const Writer = union(enum) {
+            file: std.fs.File.Writer,
+            allocating: std.Io.Writer.Allocating,
+
+            pub const Error = std.mem.Allocator.Error || std.fs.File.WriteError;
+
+            pub fn interface(this: *@This()) *std.Io.Writer {
+                return switch (this.*) {
+                    .file => |*fw| &fw.interface,
+                    .allocating => |*a| &a.writer,
+                };
             }
-        }
 
-        pub fn writer(self: *Source, allocator: std.mem.Allocator) Writer {
-            return .{ .context = .{ .self = self, .allocator = allocator } };
+            pub fn deinit(this: *@This(), source: *Source) void {
+                switch (this.*) {
+                    .file => {},
+                    .allocating => |*a| source.memory = a.toArrayList(),
+                }
+                this.* = undefined;
+            }
+        };
+
+        pub fn writer(source: *Source, allocator: std.mem.Allocator, buffer: []u8) Writer {
+            return switch (source.*) {
+                .file, .stdio => |file| .{ .file = file.writer(buffer) },
+                .memory => |*list| .{ .allocating = .fromArrayList(allocator, list) },
+                .closed => unreachable,
+            };
         }
     };
 };
@@ -721,7 +731,7 @@ fn cliDiagnosticsToErrorBundle(
     });
 
     var cur_err: ?ErrorBundle.ErrorMessage = null;
-    var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
+    var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
     defer cur_notes.deinit(gpa);
     for (diagnostics.errors.items) |err_details| {
         switch (err_details.type) {
@@ -763,10 +773,10 @@ fn diagnosticsToErrorBundle(
     try bundle.init(gpa);
     errdefer bundle.deinit();
 
-    var msg_buf: std.ArrayListUnmanaged(u8) = .empty;
-    defer msg_buf.deinit(gpa);
+    var msg_buf: std.Io.Writer.Allocating = .init(gpa);
+    defer msg_buf.deinit();
     var cur_err: ?ErrorBundle.ErrorMessage = null;
-    var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
+    var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
     defer cur_notes.deinit(gpa);
     for (diagnostics.errors.items) |err_details| {
         switch (err_details.type) {
@@ -789,7 +799,7 @@ fn diagnosticsToErrorBundle(
         const column = err_details.token.calculateColumn(source, 1, source_line_start) + 1;
 
         msg_buf.clearRetainingCapacity();
-        try err_details.render(msg_buf.writer(gpa), source, diagnostics.strings.items);
+        try err_details.render(&msg_buf.writer, source, diagnostics.strings.items);
 
         const src_loc = src_loc: {
             var src_loc: ErrorBundle.SourceLocation = .{
@@ -817,7 +827,7 @@ fn diagnosticsToErrorBundle(
                     try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
                 }
                 cur_err = .{
-                    .msg = try bundle.addString(msg_buf.items),
+                    .msg = try bundle.addString(msg_buf.written()),
                     .src_loc = src_loc,
                 };
                 cur_notes.clearRetainingCapacity();
@@ -825,7 +835,7 @@ fn diagnosticsToErrorBundle(
             .note => {
                 cur_err.?.notes_len += 1;
                 try cur_notes.append(gpa, .{
-                    .msg = try bundle.addString(msg_buf.items),
+                    .msg = try bundle.addString(msg_buf.written()),
                     .src_loc = src_loc,
                 });
             },
@@ -876,7 +886,7 @@ fn aroDiagnosticsToErrorBundle(
     var msg_writer = MsgWriter.init(gpa);
     defer msg_writer.deinit();
     var cur_err: ?ErrorBundle.ErrorMessage = null;
-    var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .empty;
+    var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
     defer cur_notes.deinit(gpa);
     for (comp.diagnostics.list.items) |msg| {
         switch (msg.kind) {
@@ -971,11 +981,11 @@ const MsgWriter = struct {
     }
 
     pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
-        m.buf.writer().print(fmt, args) catch {};
+        m.buf.print(fmt, args) catch {};
     }
 
     pub fn write(m: *MsgWriter, msg: []const u8) void {
-        m.buf.writer().writeAll(msg) catch {};
+        m.buf.appendSlice(msg) catch {};
     }
 
     pub fn setColor(m: *MsgWriter, color: std.io.tty.Color) void {
lib/compiler/resinator/preprocess.zig
@@ -18,12 +18,15 @@ pub fn preprocess(
     var driver: aro.Driver = .{ .comp = comp, .aro_name = "arocc" };
     defer driver.deinit();
 
-    var macro_buf = std.array_list.Managed(u8).init(comp.gpa);
+    var macro_buf: std.Io.Writer.Allocating = .init(comp.gpa);
     defer macro_buf.deinit();
 
-    _ = driver.parseArgs(std.io.null_writer, macro_buf.writer(), argv) catch |err| switch (err) {
+    var trash: [64]u8 = undefined;
+    var discarding: std.Io.Writer.Discarding = .init(&trash);
+    _ = driver.parseArgs(&discarding.writer, &macro_buf.writer, argv) catch |err| switch (err) {
         error.FatalError => return error.ArgError,
         error.OutOfMemory => |e| return e,
+        error.WriteFailed => return error.OutOfMemory,
     };
 
     if (hasAnyErrors(comp)) return error.ArgError;
@@ -33,7 +36,7 @@ pub fn preprocess(
         error.FatalError => return error.GeneratedSourceError,
         else => |e| return e,
     };
-    const user_macros = comp.addSourceFromBuffer("<command line>", macro_buf.items) catch |err| switch (err) {
+    const user_macros = comp.addSourceFromBuffer("<command line>", macro_buf.written()) catch |err| switch (err) {
         error.FatalError => return error.GeneratedSourceError,
         else => |e| return e,
     };
@@ -59,7 +62,9 @@ pub fn preprocess(
 
     if (hasAnyErrors(comp)) return error.PreprocessError;
 
-    try pp.prettyPrintTokens(writer, .result_only);
+    pp.prettyPrintTokens(writer, .result_only) catch |err| switch (err) {
+        error.WriteFailed => return error.OutOfMemory,
+    };
 
     if (maybe_dependencies_list) |dependencies_list| {
         for (comp.sources.values()) |comp_source| {