-
Notifications
You must be signed in to change notification settings - Fork 13
chore: upgrade to zig 0.15.2 #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR upgrades the Zig compiler version from 0.14.1 to 0.15.2, migrates collection types from std.ArrayList to std.array_list.Managed throughout the codebase, refactors writer abstractions to use *io.Writer pointers, renames token variants (removing string-quoted forms), restructures the build system with module-centric patterns, and updates dependencies in build.zig.zon. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45–75 minutes Areas requiring extra attention:
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
test/semantic/ecosystem_coverage.zig (1)
41-43: Remove unused function with outdated API.The
isTTY_()function is unused and still references the oldstd.io.getStdErr().isTTY()API that was migrated away from on line 23. Since this code is unreferenced, it should be removed as part of the Zig 0.15.2 upgrade.-fn isTTY_() bool { - return std.io.getStdErr().isTTY(); -} -src/linter/rules/must_return_ref.zig (1)
11-24: Removetryfrom line 19 documentation example.Line 19 shows
.list = try std.array_list.Managed(u32).init(std.heap.page_allocator), butManaged.init()does not return an error. Thetrykeyword should be removed. UseinitCapacity()only when you need error handling for pre-allocation. Correct syntax:.list = std.array_list.Managed(u32).init(std.heap.page_allocator)src/reporter/formatters/GithubFormatter.zig (1)
57-71: Pass&w.writerto format, not&w.The
formatfunction expects a*io.Writerparameter, but the test passes&wwherewis aWriter.Allocating. Looking atReporter.zig(line ~115), the correct pattern is to pass&w.writer. Additionally, thebufcreated at line 57 is unused—remove it or connect the writer properly to it.tasks/gen_utils.zig (1)
92-125: Adderrdefer buf.deinit()to handle memory leaks on append failuresThe function correctly uses
std.array_list.Managed(u8)andtoOwnedSlice()to transfer ownership to the caller on success. However, ifappendSlice()orappend()fail afterinitCapacity()succeeds, the allocated buffer is not freed becausedeinit()is never called.Add an
errdeferimmediately after initialization:var buf = try std.array_list.Managed(u8).initCapacity(allocator, docs.len); + errdefer buf.deinit(); var lines = mem.splitScalar(u8, docs, '\n');This ensures the buffer is properly cleaned up on error paths while preserving the success path where
toOwnedSlice()transfers ownership without requiring an explicitdeinit().
🧹 Nitpick comments (8)
src/zig.zig (1)
1-4: Module naming may cause confusion.The module is still named
@"0.14.1"but now contains Zig 0.15.x-specific API patterns (e.g.,std.Io.Writer, newstd.fmt.Formattersignature). Consider renaming this module or adding a comment explaining that it's a compatibility shim that uses 0.15.x APIs while maintaining the 0.14.1 AST/parser code.build.zig (2)
84-86: Commented-out code contains syntax error and should be cleaned up.Line 85 has invalid Zig syntax (
exe.root_module else &exe.root_module). If this code is no longer needed, consider removing the commented block entirely to avoid confusion.- // exe.want_lto - // l.link(exe.root_module else &exe.root_module, false, .{}); -
178-217: Consider removing the large commented-out check block.The 35-line commented block (lines 178-212) has been replaced with a simpler implementation. If this code is no longer needed, removing it would improve readability. If it's kept for reference, a brief comment explaining why would be helpful.
tasks/confgen.zig (1)
18-18: Buffer reuse between file writes may cause confusion.The global
bufis reused for both the Rules.zig file (line 28) and the zlint.schema.json file (line 86). While this works in a single-threaded context, it makes the code less maintainable and could lead to issues if the code is refactored for concurrency.Consider using separate local buffers for each file write:
-var buf: [1024]u8 = undefined; - pub fn main() !void { + var rules_buf: [1024]u8 = undefined; // ... - var writer = out.writer(&buf); + var writer = out.writer(&rules_buf);And similarly for the JSON schema write:
+ var schema_buf: [1024]u8 = undefined; var out = try fs.cwd().createFile(c.@"zlint.schema.json", .{}); - var writer = out.writer(&buf); + var writer = out.writer(&schema_buf);Also applies to: 86-86
src/reporter/formatters/GithubFormatter.zig (1)
60-60: Remove commented-out code.The commented line should be removed to keep the codebase clean.
- // var w = buf.writer();src/cli/print_command.zig (1)
36-42: Potential redundant flush operations.When
writer_isnull, the code sets upstdoutat line 36-40 and assignswriter = &stdout.?.interface. Then bothstdout(line 37) andwriter(line 42) have deferred flush operations. Sincewriterpoints tostdout.?.interfacein this case, this results in flushing the same writer twice.While not harmful, this is redundant.
Consider simplifying to avoid the double flush:
const sema = &sema_result.value; - var stdout: ?std.fs.File.Writer = null; - defer if (stdout) |*out| out.interface.flush() catch @panic("failed to flush writer"); var writer = writer_ orelse blk: { - stdout = std.fs.File.stdout().writer(&buf); - break :blk &stdout.?.interface; + var stdout = std.fs.File.stdout().writer(&buf); + break :blk &stdout.interface; }; defer writer.flush() catch @panic("failed to flush writer");src/linter/tester.zig (1)
111-116: Heap allocation for writer appears unnecessary.The
std.fs.File.Writeris a small struct that can live on the stack. Heap-allocating it introduces overhead and a potential leak path if an early return occurs beforedestroy. Consider using a stack allocation instead:-var buf: [512]u8 = undefined; -var writer = try self.alloc.create(std.fs.File.Writer); -writer.* = std.fs.File.stderr().writer(&buf); -defer self.alloc.destroy(writer); -var stderr = &writer.interface; -defer stderr.flush() catch @panic("failed to flush writer"); +var buf: [512]u8 = undefined; +var writer = std.fs.File.stderr().writer(&buf); +var stderr = &writer.interface; +defer stderr.flush() catch @panic("failed to flush writer");tasks/gen_utils.zig (1)
61-65: readFileAllocOptions migration looks correct; consider usingAlignment.offor clarityThe
readSourceFilecall now matches the Zig 0.15std.fs.Dir.readFileAllocOptionssignature: allocator, path,max_bytes,size_hint,std.mem.Alignment, and sentinel. Using.@"1"and sentinel0is type-correct for returning[:0]u8.If you want to avoid the magic
1and track the element type more clearly, you could optionally switch to:- return root.readFileAllocOptions(alloc, path, MAX, null, .@"1", 0); + return root.readFileAllocOptions( + alloc, + path, + MAX, + null, + std.mem.Alignment.of(u8), + 0, + );This is stylistic only; behavior remains the same.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
src/linter/rules/snapshots/homeless-try.snapis excluded by!**/*.snapsrc/linter/rules/snapshots/must-return-ref.snapis excluded by!**/*.snap
📒 Files selected for processing (54)
.github/workflows/autofix.yaml(1 hunks).github/workflows/ci.yaml(2 hunks).github/workflows/codecov.yaml(1 hunks).github/workflows/release.yaml(1 hunks).typos.toml(1 hunks)apps/site/docs/rules/must-return-ref.mdx(3 hunks)build.zig(2 hunks)build.zig.zon(2 hunks)docs/rules/must-return-ref.md(3 hunks)src/Error.zig(0 hunks)src/Semantic/SemanticBuilder.zig(1 hunks)src/Semantic/test/util.zig(2 hunks)src/cli/Options.zig(2 hunks)src/cli/lint_command.zig(4 hunks)src/cli/print_command.zig(3 hunks)src/cli/test/print_ast_test.zig(1 hunks)src/linter/LintService.zig(4 hunks)src/linter/fix.zig(1 hunks)src/linter/lint_context.zig(1 hunks)src/linter/linter.zig(1 hunks)src/linter/rules/homeless_try.zig(4 hunks)src/linter/rules/must_return_ref.zig(4 hunks)src/linter/rules/suppressed_errors.zig(1 hunks)src/linter/rules/useless_error_return.zig(2 hunks)src/linter/tester.zig(4 hunks)src/main.zig(2 hunks)src/printer/Printer.zig(5 hunks)src/printer/SemanticPrinter.zig(1 hunks)src/reporter/Reporter.zig(9 hunks)src/reporter/StringWriter.zig(3 hunks)src/reporter/formatter.zig(1 hunks)src/reporter/formatters/GithubFormatter.zig(4 hunks)src/reporter/formatters/GraphicalFormatter.zig(10 hunks)src/reporter/formatters/JSONFormatter.zig(3 hunks)src/root.zig(1 hunks)src/source.zig(1 hunks)src/util.zig(1 hunks)src/util/bitflags.zig(2 hunks)src/util/bitflags_test.zig(1 hunks)src/util/cow.zig(2 hunks)src/visit/walk.zig(3 hunks)src/zig.zig(1 hunks)src/zig/0.14.1/Ast.zig(8 hunks)src/zig/0.14.1/Parse.zig(2 hunks)src/zig/0.14.1/render.zig(12 hunks)src/zig/0.14.1/string_literal.zig(3 hunks)tasks/codegen_task.zig(0 hunks)tasks/confgen.zig(4 hunks)tasks/docgen.zig(5 hunks)tasks/gen_utils.zig(2 hunks)test/harness/TestSuite.zig(1 hunks)test/semantic/ecosystem_coverage.zig(1 hunks)test/semantic/snapshot_coverage.zig(3 hunks)zlint.schema.json(1 hunks)
💤 Files with no reviewable changes (2)
- tasks/codegen_task.zig
- src/Error.zig
🧰 Additional context used
🪛 actionlint (1.7.9)
.github/workflows/ci.yaml
79-79: condition "false" is always evaluated to false. remove the if: section
(if-cond)
🔇 Additional comments (63)
src/root.zig (2)
26-26: Test reflection addition for json module looks good.Adding
jsonto the recursive reflection test block aligns with the pattern established byprinterandzig, and ensures the exported json module is validated during testing. This change is appropriate.
3-3: Verify version constant alignment with Zig 0.15.2 upgrade.Line 3 still references
"0.14.1", but the PR objective indicates an upgrade to Zig 0.15.2. Clarify whether this version constant represents API/ABI compatibility (intentionally unchanged) or if it should be updated to reflect the new compiler version.Confirm:
- Is the version string on line 3 intentional (e.g., for API/compatibility purposes)?
- Should it be updated to
"0.15.2"as part of this upgrade?- Where is the actual Zig compiler version specified (e.g.,
build.zig.zon)?src/cli/test/print_ast_test.zig (1)
21-23: Migration tostd.array_list.Managedlooks correctUsing
std.array_list.Managed(u8).initCapacity(allocator, source.text().len)is correct here. The arena allocator (initialized at line 17 and deferred for cleanup) simplifies lifetime management—no explicitbuf.deinit()is needed. Thewriter.any()call on line 24 works as expected with this setup.src/util/cow.zig (1)
220-227: LGTM - Test updates align with Zig 0.15 formatter API.The test changes correctly use the
{f}format specifier to invoke the customformatfunction, which is the expected pattern for Zig 0.15.src/util.zig (1)
10-10: CallingConvention migration to.@"inline"/.autois correct and completeThe migration from
.Inline/.Unspecifiedto.@"inline"/.autois correct for Zig 0.14+/0.15 and is consistently applied across the codebase. All production function declarations insrc/Semantic/SemanticBuilder.zigcorrectly referencecallconv(util.@"inline"), and no legacy.Inlineor.Unspecifiedtags remain in the source tree.src/visit/walk.zig (2)
439-440: No action needed—std.array_list.Managedusage is correct for Zig 0.15.2.The initialization pattern
std.array_list.Managed(Item).init(temp.get())and deinitialization withdefer deinit()match the Zig 0.15.2 standard library documentation exactly. The implementation is already aligned with the current API.
704-704: No action needed—syntax change is correct for Zig 0.15.2.The removal of string quotes from
.usingnamespaceand.awaitaligns with Zig 0.15.x language changes: bothusingnamespaceand theasync/awaitkeywords were removed from the language spec. They are no longer reserved keywords, so the escape syntax is unnecessary. Other keywords likeif,defer, andcomptimeremain reserved and correctly retain the@"..."form.test/semantic/ecosystem_coverage.zig (2)
64-64: LGTM!The simplified test suite initialization with
.{ .test_fn = &testSemantic }is clean and aligns with the modular test infrastructure refactoring.
23-23: LGTM! The migration fromstd.io.getStdErr().isTty()tostd.fs.File.stderr().isTty()is correct for Zig 0.15.2. This API is documented in the standard library'sstd/fs/file.zig..typos.toml (1)
9-9: LGTM!Adding
build.zig.zonto the exclude list is appropriate, as build manifests often contain package names and identifiers that may trigger false positives in typo detection..github/workflows/codecov.yaml (1)
11-11: LGTM!The Zig version update to 0.15.2 aligns with the PR objectives and is consistent across all workflow files.
.github/workflows/ci.yaml (2)
33-33: LGTM!The Zig version update to 0.15.2 is consistent with the PR objectives.
75-79: Temporary workaround properly documented.The lint job is intentionally disabled due to parser incompatibility with the
usingnamespacekeyword change (keyword in 0.14 → identifier in 0.15). The TODO comment clearly indicates this should be re-enabled after full migration..github/workflows/autofix.yaml (1)
15-15: LGTM!The Zig version update to 0.15.2 aligns with the PR objectives.
.github/workflows/release.yaml (1)
9-9: LGTM!The Zig version update to 0.15.2 is consistent with the PR objectives.
src/printer/SemanticPrinter.zig (1)
120-120: LGTM!The migration from
std.ArrayListtostd.array_list.Managedaligns with the broader PR objective to use managed collection types throughout the codebase.src/Semantic/test/util.zig (2)
40-40: LGTM!The migration to
toManaged(t.allocator)aligns with the PR's broader migration to managed collection types, and the OOM panic provides appropriate error handling for test utilities.
12-18: The writer initialization syntax is correct and valid for this codebase.The pattern
std.fs.File.stderr().writer(&buf)followed by accessing.interfaceis the standard approach used throughout the zlint codebase. This same pattern appears insrc/main.zig,src/cli/Options.zig,src/cli/lint_command.zig, andsrc/linter/tester.zig, indicating it is a well-established convention compatible with Zig 0.15.0+. The File.writer() method accepts a buffer parameter and returns a writer struct with an interface field, making this syntax correct and idiomatic for this project.Likely an incorrect or invalid review comment.
src/linter/lint_context.zig (1)
272-273: LGTM! Type migration tostd.array_list.Managedis correct.The migration from
std.ArrayListtostd.array_list.Managedaligns with Zig 0.15.x naming conventions. The TODO comment on line 272 could be clarified - it mentionsManaged(Error)but the actual type isManaged(Diagnostic).src/linter/rules/useless_error_return.zig (2)
211-211: LGTM! Type migration consistent with Zig 0.15.x.The
err_stackfield correctly updated tostd.array_list.Managed(ErrState).
255-260: LGTM! Initialization updated correctly.The
initfunction properly usesstd.array_list.Managed(ErrState).init(alloc)matching the field type change.src/zig.zig (1)
14-42: New escape formatting utilities look correct.The
fmtEscapesandstringEscapeimplementations properly handle:
- Common escape sequences (
\n,\r,\t,\\,\")- Single quotes passed through (correct for double-quoted string context)
- Printable ASCII range written directly
- Non-printable bytes escaped as
\xNNsrc/zig/0.14.1/string_literal.zig (3)
47-47: LGTM! Signature updated for Zig 0.15.x writer interface.The
formatMessagefunction now takes a concrete*std.io.Writerpointer instead of a genericanytypewriter, returningstd.io.Writer.Error!void. This aligns with the broader PR pattern of migrating to explicit writer types.
89-89: LGTM! Formatter signature updated for Zig 0.15.x.The
std.fmt.Formatter(FormatMessage, formatMessage)now takes two type parameters (data type and format function) matching the new Zig 0.15.x API.
359-359: LGTM! Buffer type migrated to Managed.Consistent with the project-wide migration from
std.ArrayListtostd.array_list.Managed.build.zig.zon (1)
29-40: Dependency chameleon version has a compatibility concern with Zig 0.15.x.smart-pointers v0.0.4 requires
0.15.0and recover v1.3.0 requires0.14.0, both compatible with Zig 0.15.x. However, chameleon pinned to commit 414169de specifiesminimum_zig_version = "0.15.0-dev.1228+6dbcc3bd5", which targets a specific development build rather than the stable Zig 0.15.x release. Verify this specific dev version is available in your target toolchain or consider using a commit that targets stable Zig 0.15.0.src/cli/Options.zig (2)
108-117: Writer API usage is correct for Zig 0.15.x.The pattern
std.fs.File.stdout().writer(&buf)with.interfaceaccess is the proper Zig 0.15.x file writer API. The 512-byte buffer is sufficient for the ~500 character help text, and the deferredflush()call ensures the output is properly written before exit.
46-52: Remove unused error types from ParseError or verify actual writer error behavior.The
try stdout.writeAll(...)calls will propagate errors matching the buffered writer's actual error set. However,WriterErrorandWriteFailedare defined inParseErrorbut never returned by thestdout.writeAll()calls. For a buffered writer to stdout, the error set is typically empty (stdout is always writable), making these error types unnecessary. Either remove them fromParseErroror explicitly document and handle the specific error cases they represent.src/cli/lint_command.zig (1)
25-31: Heap-allocated writer pattern for cross-thread use looks correct.The comment explains the rationale well. The defer ordering ensures flush happens before destroy.
src/printer/Printer.zig (1)
66-77: JSON stringify pattern updated correctly for Zig 0.15.The migration to
std.json.Stringify{ .writer = self.writer }with.write(value)follows the new API pattern.src/zig/0.14.1/render.zig (4)
10-17: Import and type migrations for Zig 0.15 look correct.The changes to use
zig.fmtEscapesandstd.array_list.Managedalign with the Zig 0.15 API.
285-294: Token variant renames align with Zig 0.15 changes.The changes from quoted token variants (
.@"usingnamespace",.@"await") to bare identifiers (.usingnamespace,.await) are correct for Zig 0.15.Also applies to: 626-632, 919-919
1621-1621: Format specifier change forfmtEscapesis correct.Using
"{any}"forfmtEscapesoutput aligns with Zig 0.15 formatting requirements.
3327-3376:AutoIndentingStreamupdates for Zig 0.15 are consistent.The migration to
GenericWriterandstd.array_list.Managedfor internal stacks follows the Zig 0.15 API changes correctly.src/util/bitflags.zig (1)
140-156: Format function signature is incompatible withstd.fmt.The
formatfunction is missing required parameters forstd.fmtcompatibility. The standard format interface requirescomptime fmt: []const u8andstd.fmt.FormatOptionsparameters, along with a return type ofError!void. The current signature lacks these, which means the"{f}"format specifier in tests will not work correctly with this implementation.To fix, update the signature to:
fn format(self: Flags, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !voidbuild.zig (1)
47-64: Module-centric build pattern looks correct for Zig 0.15.x.The migration from
addStaticLibrarywithroot_source_filetocreateModule+addLibrarywithroot_modulefollows the new Zig 0.15.x patterns correctly.src/Semantic/SemanticBuilder.zig (1)
456-461: Token tag renames align with Zig 0.15.x changes.The switch from
.@"await"to.awaitand.@"usingnamespace"to.usingnamespacereflects the updated token tag enum in Zig 0.15.x standard library.src/linter/fix.zig (1)
124-126: Migration tostd.array_list.Managedis correct.The change from
std.ArrayList(Diagnostic)tostd.array_list.Managed(Diagnostic).init(alloc)aligns with Zig 0.15.x naming conventions whereArrayListwas renamed toarray_list.Managed.src/linter/rules/must_return_ref.zig (1)
138-156: Type registry correctly updated to includeManaged.Adding
"Managed"totypes_to_checkensures the rule catchesstd.array_list.Managedreturn types, maintaining the rule's effectiveness after the Zig 0.15.x migration.src/source.zig (1)
22-26: File metadata API migration looks correct for Zig 0.15.x.The changes from
file.metadata()tofile.stat()and frommeta.size()method call tometa.sizefield access align with the Zig 0.15.x standard library API changes.test/harness/TestSuite.zig (1)
134-159: Buffered snapshot writer integration looks solidUsing a fixed stack buffer with
snapshot.writer(&buf), deferringw.interface.flush(), and routing all prints throughw.interface.printis consistent with the new writer API and should give reliable snapshot output in tests.src/reporter/formatter.zig (1)
34-38: FormatError alias matches the new io.Writer-based reporter APISwitching
FormatErrortoio.Writer.Error || Allocator.Errorand introducing theioalias aligns this module with the updated reporter/writer interfaces without changing observable behavior.src/main.zig (1)
5-6: Main import and writer refactors look consistent with the Zig 0.15 toolchainThe non-relative imports, the adjusted error printing (
err.message.borrow()plus{any}forOptions.usage), and the use ofstd.fs.File.stdout().writer(&.{})withstdout.interface.printall match the project-wide shift to the new writer API and should behave equivalently to the previous implementation.Also applies to: 10-11, 33-36, 41-45
src/zig/0.14.1/Parse.zig (1)
680-692: Parser tag updates to.usingnamespaceand.awaitare in line with AST enum changesUpdating the node tags in
expectUsingNamespaceandparsePrefixExprto.usingnamespaceand.awaitis the right adjustment to stay consistent with the renamedAst.Node.Tagvariants; no behavioral issues expected as long asAst.zigmatches.Also applies to: 1745-1753
src/linter/rules/homeless_try.zig (1)
217-219: Test fixtures updated to std.array_list.Managed are consistent with the new list APISwitching the homeless_try rule’s example code from
std.ArrayListtostd.array_list.Managed(both for*u8andu32cases) keeps the tests aligned with the new standard container type without affecting the rule’s core logic.Also applies to: 268-275, 277-283, 338-345
src/linter/LintService.zig (2)
80-90: Error collection migration to std.array_list.Managed(Error) is coherentUsing
?std.array_list.Managed(Error)as the out-parameter type inlintSourceand wiring it through the semantic, analysis, and fix-disabled paths (including theinitCapacity/appendAssumeCapacityconversion from diagnostics) results in a single, consistent managed error list that the caller (and reporter) can own and deinit; the control flow in thecatchblock oftryLintFilestill preserves the original linting error semantics.Also applies to: 99-149
151-170: applyFixes correctly returns a managed error list without double-freeingConverting
result.unfixed_errorsto astd.array_list.Managed(Error)viatoManaged(self.allocator)and then resettingresult.unfixed_errors = .{}beforeresult.deinit(self.allocator)ensures the unfixed errors are moved out safely and avoids deinitializing the same storage twice.src/linter/linter.zig (1)
75-81: runOnSource now returning Managed diagnostics matches the linter’s new ownership modelUpdating the
errorsout-parameter to*?std.array_list.Managed(Context.Diagnostic)and continuing to assignctx.takeDiagnostics()keeps the public API in sync with the Managed diagnostic list type while preserving the existing “null on success, non-null pluserror.LintingFailedon failure” contract.Also applies to: 143-145
test/semantic/snapshot_coverage.zig (1)
73-76: Snapshot coverage tests correctly adopt buffered writers and the new Reporter APIBoth
runPassandrunFailnow use stack-backed buffered writers with explicit flush and pass&writer.interfaceintoPrinter.init/Reporter.init, and the addedtryaroundreporter.reportErrorSlicecleanly propagates I/O/formatting failures to the harness without changing the intended pass/fail semantics of these tests.Also applies to: 112-119, 126-133
tasks/docgen.zig (1)
45-45: Good use of buffered writer pattern.The global buffer is properly initialized for each document file generation, ensuring no cross-contamination between writes. The deferred flush handling at line 100 ensures data is written before the file closes.
Also applies to: 99-101
zlint.schema.json (1)
432-434: Documentation correctly updated to reflect API changes.The schema documentation has been updated to use
std.array_list.Managed(u32)instead ofstd.ArrayList(u32), which aligns with the broader migration in this PR.src/reporter/formatters/JSONFormatter.zig (1)
15-18: Clean migration to io.Writer interface.The formatter now uses
*io.Writerandstd.json.Stringifydirectly, which aligns with the broader refactoring across the codebase.docs/rules/must-return-ref.md (1)
18-26: Documentation examples correctly updated.The examples have been updated to use
std.array_list.Managed(u32)instead ofstd.ArrayList(u32), maintaining consistency with the codebase migration.src/reporter/StringWriter.zig (2)
4-4: Well-designed writer interface separation.The separation between
GenericWriter(for generic writing) andioWriter()(returningio.Writer) provides clear, flexible APIs for different use cases. The rename fromWritertoGenericWriterimproves clarity.Also applies to: 30-36
7-7: Theundefinedinitialization is valid — no fix needed for that.Zig's
io.Writer.interface()accepts undefined buffers as backing storage; it doesn't initialize them. The writer maintains an end index starting from 0, so uninitialized data is never accessed. This pattern is standard in Zig (see stdlib examples).However,
writer_bufferappears to be unused: thewrite()method writes toself.buf(the ArrayList), not towriter_buffer. Consider whetherioWriter()is actually needed or if this is dead code.Likely an incorrect or invalid review comment.
apps/site/docs/rules/must-return-ref.mdx (1)
20-21: Documentation correctly updated for Zig 0.15 API migration.The examples properly reflect the migration from
std.ArrayListtostd.array_list.Managed, which aligns with the Zig 0.15.2 upgrade.src/linter/tester.zig (1)
327-334: Snapshot writing implementation looks correct.The buffered writer pattern with a fixed 8KB buffer and explicit flush is appropriate for file I/O operations.
src/zig/0.14.1/Ast.zig (2)
513-513: Token variant renames align with Zig 0.15 changes.The removal of
@""quoting for reserved word enum variants (.await,.usingnamespace) is consistent with Zig 0.15's language changes.
468-476: Error formatting correctly updated for Zig 0.15.The change from
'{'}to'{f}'withzig.fmtEscapesis the correct pattern for formatting escaped bytes in Zig 0.15+.src/reporter/formatters/GraphicalFormatter.zig (2)
31-37: Writer interface migration is consistent.The migration from the
Writeralias to*io.Writeris consistently applied across all public methods in this formatter.
473-483: Replace customwriteByteNTimeswithio.Writer's built-in method.The
writeByteNTimesfunction duplicates functionality already provided bystd.io.Writer. The built-in method is more efficient (buffering writes in 256-byte chunks instead of single bytes). Replace the custom implementation:fn writeByteNTimes(w: *io.Writer, byte: u8, n: usize) FormatError!void { - for (0..n) |_| { - try w.writeByte(byte); - } + try w.writeByteNTimes(byte, n); }Keep
writeBytesNTimesas-is—there is no equivalent built-in method.Likely an incorrect or invalid review comment.
src/reporter/Reporter.zig (2)
118-137: Buffering strategy change enables atomic writes.The new pattern of collecting all formatted output into a
Writer.Allocatingbuffer before writing atomically toself.writerunder a lock ensures that diagnostic output from concurrent threads won't be interleaved. This is a good improvement for multi-threaded linting.
190-192: Atomic initialization updated for Zig 0.15.The
.init(0)pattern forstd.atomic.Valueis the correct idiom for Zig 0.15+.
| // NOTE: we use 0.14.1, but 0.14.0 should still work. | ||
| .minimum_zig_version = "0.14.0", | ||
| .minimum_zig_version = "0.15.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale comment on line 18.
The comment "NOTE: we use 0.14.1, but 0.14.0 should still work" is now outdated since minimum_zig_version is set to 0.15.0. Update or remove this comment to reflect the actual Zig version requirements.
- // NOTE: we use 0.14.1, but 0.14.0 should still work.
+ // NOTE: we use 0.15.2, but 0.15.0 should still work.
.minimum_zig_version = "0.15.0",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // NOTE: we use 0.14.1, but 0.14.0 should still work. | |
| .minimum_zig_version = "0.14.0", | |
| .minimum_zig_version = "0.15.0", | |
| // NOTE: we use 0.15.2, but 0.15.0 should still work. | |
| .minimum_zig_version = "0.15.0", |
🤖 Prompt for AI Agents
In build.zig.zon around lines 18 to 19, the inline comment stating "NOTE: we use
0.14.1, but 0.14.0 should still work" is stale because minimum_zig_version is
now "0.15.0"; update the comment to reflect the current version requirement
(e.g., change it to mention 0.15.0 and any compatible older versions) or remove
the comment entirely so it no longer contradicts .minimum_zig_version.
| var buf: [4096]u8 = undefined; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global mutable buffer may cause thread-safety issues.
The module-level var buf is used by the stdout writer which is shared across threads via the Reporter. If multiple threads write concurrently, this buffer could be corrupted.
Consider making this buffer local to the lint function or using thread-local storage.
-var buf: [4096]u8 = undefined;
-
pub fn lint(alloc: Allocator, options: Options) !u8 {
+ var buf: [4096]u8 = undefined;
// writer cannot live on the stack.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| var buf: [4096]u8 = undefined; | |
| pub fn lint(alloc: Allocator, options: Options) !u8 { | |
| var buf: [4096]u8 = undefined; | |
| // writer cannot live on the stack. |
🤖 Prompt for AI Agents
In src/cli/lint_command.zig around lines 22-23 the module-level mutable buffer
`var buf: [4096]u8` is shared across threads and can be corrupted by concurrent
Reporter stdout writes; move the buffer out of module scope and make it
per-invocation or per-thread: remove the global `buf`, declare a local `buf`
inside the `lint` function (or the function that constructs the Reporter/writer)
or mark it thread-local (e.g., with @threadLocal) so each thread gets its own
buffer, and update all references to use the new local/thread-local buffer (or
allocate a buffer from an allocator) to eliminate the race.
| pub fn readUntilDelimiterOrEof(self: *std.io.Reader, buffer: []u8, delimiter: u8) anyerror!?[]u8 { | ||
| var fbw = std.io.Writer.fixed(buffer); | ||
| const bytes_read = self.streamDelimiter(&fbw, delimiter) catch |err| switch (err) { | ||
| error.EndOfStream => if (fbw.end == 0) { | ||
| return null; | ||
| } else return err, | ||
|
|
||
| else => |e| return e, | ||
| }; | ||
| if (bytes_read == 0) return null; | ||
| self.toss(1); // throw out the delimiter | ||
| return buffer[0..bytes_read]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential bug in EndOfStream handling with partial data.
When EndOfStream is encountered but fbw.end != 0 (meaning some data was read), the function returns the error instead of the partial data. This seems incorrect - the partial data should be returned.
Also, the comment says "discards the delimiter" but on EndOfStream there is no delimiter to discard, yet toss(1) is still called on line 203 after reading non-zero bytes.
pub fn readUntilDelimiterOrEof(self: *std.io.Reader, buffer: []u8, delimiter: u8) anyerror!?[]u8 {
var fbw = std.io.Writer.fixed(buffer);
const bytes_read = self.streamDelimiter(&fbw, delimiter) catch |err| switch (err) {
error.EndOfStream => if (fbw.end == 0) {
return null;
- } else return err,
+ } else {
+ return buffer[0..fbw.end];
+ },
else => |e| return e,
};
if (bytes_read == 0) return null;
self.toss(1); // throw out the delimiter
return buffer[0..bytes_read];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn readUntilDelimiterOrEof(self: *std.io.Reader, buffer: []u8, delimiter: u8) anyerror!?[]u8 { | |
| var fbw = std.io.Writer.fixed(buffer); | |
| const bytes_read = self.streamDelimiter(&fbw, delimiter) catch |err| switch (err) { | |
| error.EndOfStream => if (fbw.end == 0) { | |
| return null; | |
| } else return err, | |
| else => |e| return e, | |
| }; | |
| if (bytes_read == 0) return null; | |
| self.toss(1); // throw out the delimiter | |
| return buffer[0..bytes_read]; | |
| } | |
| pub fn readUntilDelimiterOrEof(self: *std.io.Reader, buffer: []u8, delimiter: u8) anyerror!?[]u8 { | |
| var fbw = std.io.Writer.fixed(buffer); | |
| const bytes_read = self.streamDelimiter(&fbw, delimiter) catch |err| switch (err) { | |
| error.EndOfStream => if (fbw.end == 0) { | |
| return null; | |
| } else { | |
| return buffer[0..fbw.end]; | |
| }, | |
| else => |e| return e, | |
| }; | |
| if (bytes_read == 0) return null; | |
| self.toss(1); // throw out the delimiter | |
| return buffer[0..bytes_read]; | |
| } |
🤖 Prompt for AI Agents
In src/cli/lint_command.zig around lines 193-205, the EndOfStream branch
incorrectly returns an error when partial data exists and always calls toss(1)
even though no delimiter was read; change the error handling so that when
streamDelimiter returns EndOfStream but fbw.end > 0 you treat that as a
successful partial read (set bytes_read = fbw.end and continue to return the
slice) instead of returning the error, and only call self.toss(1) when the
delimiter was actually consumed (i.e., when streamDelimiter succeeded without
EndOfStream).
| .{"writeByteNTimes"}, // note: removed in 0.15 | ||
| .{"writeBytesNTimes"}, // note: removed in 0.15 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Zig 0.15.2 std.io.Writer writeByteNTimes writeBytesNTimes removed
💡 Result:
They were removed as part of the big std.io/std.Io overhaul in the 0.15+ series ("Writergate"). The old std.io Writer helpers (including writeByteNTimes / writeBytesNTimes) were deleted or deprecated in favor of the new non‑generic std.Io.Writer and the new File/Writer APIs (e.g. std.fs.File.stdout().writer(&buf).interface, writeAll, the new Writer.print/drain style). See the 0.15 release notes and write‑up on the new Writer for details and migration guidance. [1][2]
Sources:
- Zig 0.15.1 release notes (deletions / Writergate). [1]
- Article / blog describing the new std.Io.Writer and its API changes. [2]
Remove the stale method references from the printMethods map.
The methods writeByteNTimes and writeBytesNTimes were removed in Zig 0.15+ as part of the "Writergate" std.io/std.Io overhaul. Since these methods no longer exist in the standard library, the entries at lines 186-187 should be deleted to avoid maintaining incorrect references.
🤖 Prompt for AI Agents
In src/linter/rules/suppressed_errors.zig around lines 186-187, remove the two
stale entries .{"writeByteNTimes"} and .{"writeBytesNTimes"} from the
printMethods map so the map no longer references methods removed in Zig 0.15;
update surrounding comma placement if needed to keep valid Zig list syntax and
run tests/formatting to ensure the file compiles.
| const ContainerKind = enum { object, array }; | ||
| const ContainerStack = std.ArrayList(ContainerKind); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistency between ArrayList and Managed usage.
Line 18 declares ContainerStack = std.ArrayList(ContainerKind), but line 35 calls container_stack.deinit(self.alloc) which is the Managed API. std.ArrayList.deinit() takes no arguments.
Similarly, lines 125 and 133 call append(self.alloc, ...) which is the Managed API, not ArrayList.
const ContainerKind = enum { object, array };
-const ContainerStack = std.ArrayList(ContainerKind);
+const ContainerStack = std.array_list.Managed(ContainerKind);
const NEWLINE = if (builtin.target.os.tag == .windows) "\r\n" else "\n";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const ContainerKind = enum { object, array }; | |
| const ContainerStack = std.ArrayList(ContainerKind); | |
| const ContainerKind = enum { object, array }; | |
| const ContainerStack = std.array_list.Managed(ContainerKind); |
🤖 Prompt for AI Agents
In src/printer/Printer.zig around lines 17-18 (and usages at ~35, 125, 133): you
declared ContainerStack as std.ArrayList(ContainerKind) but are calling the
Managed API (deinit(self.alloc), append(self.alloc, ...)). Fix by making the
APIs consistent: either change the type to the Managed wrapper (ContainerStack =
std.ArrayList(ContainerKind).Managed) and keep the current calls
(Managed.init(self.alloc), deinit(self.alloc), append(self.alloc, ...)), or keep
std.ArrayList(ContainerKind) and update call sites to the non-Managed API
(initialize with ContainerStack.init(self.alloc) or
ContainerStack.init(allocator) as appropriate, call container_stack.deinit()
with no allocator, and call container_stack.append(value) without passing
alloc). Ensure init/deinit and append usages match the chosen ArrayList variant.
| pub fn deinit(self: *Reporter) void { | ||
| self.writer.flush() catch {}; | ||
| self.writer_lock.lock(); | ||
| self.writer.flush() catch |e| std.debug.panic("Reporter failed to flush writer: {s}", .{@errorName(e)}); | ||
| self.vtable.deinit(self.ptr, self.alloc); | ||
| self.vtable.destroy(self.ptr, self.alloc); | ||
| self.writer = undefined; | ||
|
|
||
| if (comptime util.IS_DEBUG) { | ||
| self.vtable.format = &PanicFormatter.format; | ||
| self.vtable.deinit = &PanicFormatter.deinit; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing unlock in deinit - potential deadlock on re-entry.
The lock is acquired at line 164 but never released. While deinit typically isn't called multiple times, this could cause issues if the reporter is reused or if there's any debug logging that tries to use the reporter during cleanup. Additionally, the lock should be released before setting writer = undefined:
pub fn deinit(self: *Reporter) void {
self.writer_lock.lock();
+ defer self.writer_lock.unlock();
self.writer.flush() catch |e| std.debug.panic("Reporter failed to flush writer: {s}", .{@errorName(e)});
self.vtable.deinit(self.ptr, self.alloc);
self.vtable.destroy(self.ptr, self.alloc);
self.writer = undefined;
if (comptime util.IS_DEBUG) {
self.vtable.format = &PanicFormatter.format;
self.vtable.deinit = &PanicFormatter.deinit;
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn deinit(self: *Reporter) void { | |
| self.writer.flush() catch {}; | |
| self.writer_lock.lock(); | |
| self.writer.flush() catch |e| std.debug.panic("Reporter failed to flush writer: {s}", .{@errorName(e)}); | |
| self.vtable.deinit(self.ptr, self.alloc); | |
| self.vtable.destroy(self.ptr, self.alloc); | |
| self.writer = undefined; | |
| if (comptime util.IS_DEBUG) { | |
| self.vtable.format = &PanicFormatter.format; | |
| self.vtable.deinit = &PanicFormatter.deinit; | |
| } | |
| } | |
| pub fn deinit(self: *Reporter) void { | |
| self.writer_lock.lock(); | |
| defer self.writer_lock.unlock(); | |
| self.writer.flush() catch |e| std.debug.panic("Reporter failed to flush writer: {s}", .{@errorName(e)}); | |
| self.vtable.deinit(self.ptr, self.alloc); | |
| self.vtable.destroy(self.ptr, self.alloc); | |
| self.writer = undefined; | |
| if (comptime util.IS_DEBUG) { | |
| self.vtable.format = &PanicFormatter.format; | |
| self.vtable.deinit = &PanicFormatter.deinit; | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/reporter/Reporter.zig around lines 163-174, the writer_lock is acquired
but never released which can deadlock on re-entry; change deinit so the lock is
released after flushing (and in the error path as well) and before mutating
writer (setting writer = undefined) — use a defer or explicit unlock to ensure
unlock happens even if flush errors, and ensure the unlock happens prior to
calling vtable.deinit/destroy or changing writer.
| try expectFmt("0", "{d}", .{empty.repr()}); | ||
| const name = "util.bitflags_test.TestFlags"; | ||
| try expectFmt(name ++ "()", "{}", .{empty}); | ||
| try expectFmt(name ++ "(a | c)", "{}", .{some}); | ||
| try expectFmt(name ++ "(a | b | c | d)", "{}", .{all}); | ||
| try expectFmt(name ++ "()", "{f}", .{empty}); | ||
| try expectFmt(name ++ "(a | c)", "{f}", .{some}); | ||
| try expectFmt(name ++ "(a | b | c | d)", "{f}", .{all}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the bitflags files
find . -name "bitflags*" -type fRepository: DonIsaac/zlint
Length of output: 110
🏁 Script executed:
# Examine the bitflags.zig file to see the format function signature
cat -n src/util/bitflags.zig | head -100Repository: DonIsaac/zlint
Length of output: 4503
🏁 Script executed:
# Look at the full bitflags_test.zig file to understand context
wc -l src/util/bitflags_test.zigRepository: DonIsaac/zlint
Length of output: 88
🏁 Script executed:
# Continue viewing bitflags.zig to find the format function
sed -n '100,200p' src/util/bitflags.zigRepository: DonIsaac/zlint
Length of output: 3517
🏁 Script executed:
# View the test file around the lines in question (127-131)
sed -n '120,132p' src/util/bitflags_test.zigRepository: DonIsaac/zlint
Length of output: 511
🏁 Script executed:
# Let me check Zig's std.fmt to understand the expected format function signature
# Search for how format functions are typically implemented in Zig
find . -type f -name "*.zig" | xargs grep -l "@TypeOf.*format" | head -5Repository: DonIsaac/zlint
Length of output: 95
🏁 Script executed:
# Check if there are any other examples of custom format implementations in this codebase
rg "pub fn format\(" --type zig -A 2 | head -30Repository: DonIsaac/zlint
Length of output: 2086
Format function signature is incompatible with Zig's std.fmt protocol; tests using "{f}" format specifier will fail.
The format function in bitflags.zig has signature format(self: Flags, writer: *std.Io.Writer), but Zig's std.fmt expects the signature format(self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype). Without the format string and options parameters, std.fmt will not invoke the custom format function.
The tests on lines 129–131 using "{f}" will not work as intended. Line 127 using "{d}" with empty.repr() will work correctly since it formats the integer directly.
Update the format function signature to match the std.fmt protocol, as shown in src/util/id.zig for reference.
🤖 Prompt for AI Agents
In src/util/bitflags_test.zig around lines 127 to 131, the tests using the "{f}"
specifier fail because the Flags.format function has the wrong signature; update
the format function in src/util/bitflags.zig to match Zig's std.fmt protocol
(format(self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer:
anytype)) like src/util/id.zig does, then adapt its implementation to use the
provided fmt/options/writer (write the integer repr for "{d}" and the
human-friendly flag names for "{f}", handling empty/all cases and composing
strings via the writer) so std.fmt will invoke it correctly.
| }; | ||
| } | ||
|
|
||
| /// Use a `{s}` specifier to print the contained string. Use `{}` or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the outdated comment.
The comment references format specifiers like {s}, {}, and {any}, but the new implementation no longer accepts format specifiers. Update this comment to reflect that the format function now writes the string directly using the Zig 0.15 Writer API.
Apply this diff to update the comment:
- /// Use a `{s}` specifier to print the contained string. Use `{}` or
+ /// Format function for the new Zig 0.15 Writer API. Use `{f}` to invoke.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Use a `{s}` specifier to print the contained string. Use `{}` or | |
| /// Format function for the new Zig 0.15 Writer API. Use `{f}` to invoke. |
🤖 Prompt for AI Agents
In src/util/cow.zig around line 154, the comment is outdated because it mentions
format specifiers like `{s}`, `{}`, and `{any}` which are no longer supported;
update the comment to state that the format function now writes the contained
string directly using the Zig 0.15 Writer API and does not accept format
specifiers, remove any references to old specifiers, and briefly note that
callers should pass the writer and string directly (or rely on the Writer API)
instead of using format tokens.
| pub fn format(self: Self, writer: *std.Io.Writer) std.Io.Writer.Error!void { | ||
| return writer.writeAll(self.str); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Zig 0.15.2 standard library Writer type std.io.Writer
💡 Result:
Short summary — Zig 0.15.x std.io.Writer
-
In 0.15.x the io API was redesigned: Writer is no longer the old generic std.io.Writer; the new writer interface places the buffer "above the vtable" (buffer is part of the interface) to enable zero-copy/hot-path optimizations and explicit buffering. This is a breaking change from pre-0.15 usage. [1][2]
-
Practical effect: you create a writer from a File (or other sink) by supplying a buffer, then call methods on that writer (write/writeAll/print/flush, etc.). Formatted printing now requires a buffer-backed writer (or you must allocate the formatted string first). Example pattern:
var out_buf: [4096]u8 = undefined;
var w = try std.fs.File.stdout().writer(&out_buf);
try w.print("Hello {s}\n", .{"world"});
try w.flush();
(See docs/examples). [1][3] -
Migration notes / release commentary: the change was documented in the 0.15 release notes as a major overhaul of Reader/Writer (nicknamed “Writergate” in community writeups); code using the old generic Writer must be updated to the new buffer-driven API. [2][4]
Sources
- Zig docs (0.15.*) — standard library I/O examples and API notes. [1]
- 0.15 release notes describing the Reader/Writer redesign. [2]
- Tutorial / examples showing std.io.Writer usage and custom Writer implementation. [3]
If you want, I can paste the exact API signatures and a minimal copy-paste migration example for your codebase.
🏁 Script executed:
# Search for Writer usage patterns in the codebase
rg "std\.I[io]\.Writer" --type zig -A 2 -B 2Repository: DonIsaac/zlint
Length of output: 1000
🏁 Script executed:
# Check if there are other imports or usages of io/Io module
rg "std\.(io|Io)" --type zig | head -20Repository: DonIsaac/zlint
Length of output: 1715
Change std.Io.Writer to std.io.Writer (with lowercase io).
In Zig 0.15+, the I/O module is std.io, not std.Io. This should be std.io.Writer and std.io.Writer.Error.
🤖 Prompt for AI Agents
In src/util/cow.zig around lines 156-157 the function signature uses the wrong
I/O module casing; replace occurrences of std.Io.Writer and std.Io.Writer.Error
with std.io.Writer and std.io.Writer.Error respectively so the signature reads
using std.io types and the return/error types match Zig 0.15+.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.