From 98201a307064c17368adf10dcd1e0876f9b2c204 Mon Sep 17 00:00:00 2001 From: "bjorn.ahl" Date: Thu, 10 Sep 2026 21:24:45 +0200 Subject: [PATCH] =?UTF-8?q?build(toolchain):=20bump=20Zig=20to=20dev.2085?= =?UTF-8?q?=20=E2=80=94=20@hasDecl=20is=20now=20pub-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `nix flake update` moved the zig-overlay pin from 0.17.0-dev.1786+75044cb04 to 0.17.0-dev.2085+5e36170b5, and every `zig build` broke with 26 comptime errors of the form: src/grants.zig:186:13: error: type 'grants.FakeProbes' is not Grant Probes: missing method 'micGranted' Cause: upstream Zig narrowed `@hasDecl` to report only *public* declarations. The langref shipped with each compiler states the change outright — dev.1786 says "has a declaration matching name", dev.2085 says "has a **public** declaration matching name". Confirmed by differential probe on both toolchains: `@hasDecl(T, "m")` for a non-pub `m`, queried from T's own file, returns true on dev.1786 and false on dev.2085. The five comptime contract assertions (`assertProbes`, `assertHelper`, `assertTransport`, `assertDeps` x2) check a type's method surface with `@hasDecl`. Every production adapter already declares those methods `pub`; the test fakes declared them bare `fn`, which was legal only because the assertion sits in the same file as the fake and same-file visibility used to satisfy `@hasDecl`. Under the new rule the fakes stop matching their own contracts and each assertion fires `@compileError`. Fix: `pub` on the contract methods of FakeProbes, FakeDeps (undo and insertion_runner), FakeHelper, and FakeTransport, plus FakeTransport's `Reader` handle. Nothing else changes — no runtime behaviour, no production code. Test-only helpers such as `FakeProbes.requested` stay private, so the `pub` marker now says precisely which members are the contract. That the seam was always fragile is visible in std: `std.meta.hasFn` lives in a different file and therefore already returned false for private decls on dev.1786. These hand-rolled checks were leaning on a visibility rule std's own helper never offered. Worth knowing for the next reader: `local_backend.zig:194`'s `if (comptime !@hasDecl(Helper, "usesModel")) return true;` is the one *optional* `@hasDecl` in the tree — it degrades silently instead of erroring, and under the new semantics it would have quietly reported "installation still valid" forever rather than failing to compile. It is saved only because `usesModel` is also in `assertHelper`'s required list, so the hard assertion fires first. That redundancy is load-bearing. Verified: `zig build`, `zig build test`, and `zig build install-agent` all exit 0 on dev.2085 (install signed pair-1789068005-16478). Per docs/toolchain.md this bump is deliberately partial — see the PR body. --- flake.lock | 12 ++++++------ src/grants.zig | 12 ++++++------ src/insertion_runner.zig | 18 +++++++++--------- src/local_backend.zig | 18 +++++++++--------- src/session.zig | 20 ++++++++++---------- src/undo.zig | 12 ++++++------ 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/flake.lock b/flake.lock index e7c81a3..222e6f7 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1787001381, - "narHash": "sha256-Ue1Yo8gfHdD4TMtNewhA4tkSYeFqXThju0nCyJc3ALo=", + "lastModified": 1789006805, + "narHash": "sha256-xB8mKMOx1IA9vTDNLmJZ6n4wCMq/cuWBBOzGCRnqxrU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ec2d622de0773551768cf98f3fc50cbcc003b9c5", + "rev": "8ce4ef6cb6f871616146b9fe26d2a5ae594e94fe", "type": "github" }, "original": { @@ -111,11 +111,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1787055166, - "narHash": "sha256-8CJY4I5765THUNvhl3VWD5WRcr7bwcVYb57BFoehbbw=", + "lastModified": 1788915085, + "narHash": "sha256-As2fvcu6jU1Br1e7XCZLjNLSQhG3jP+MYxnLrz5hnGs=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "221691aa23c6b6a349b46ab85fda2b84ee321781", + "rev": "f335703b4cd05d4d335f8565706ec4bd99ea2f65", "type": "github" }, "original": { diff --git a/src/grants.zig b/src/grants.zig index 718affc..40f2f6d 100644 --- a/src/grants.zig +++ b/src/grants.zig @@ -379,23 +379,23 @@ const FakeProbes = struct { requests: [8]Grant = undefined, request_count: usize = 0, - fn micGranted(self: *FakeProbes) bool { + pub fn micGranted(self: *FakeProbes) bool { return self.mic; } - fn listenGranted(self: *FakeProbes) bool { + pub fn listenGranted(self: *FakeProbes) bool { return self.listen; } - fn tapEnabled(self: *FakeProbes) bool { + pub fn tapEnabled(self: *FakeProbes) bool { return self.tap; } - fn postEventGranted(self: *FakeProbes) bool { + pub fn postEventGranted(self: *FakeProbes) bool { return self.post_event; } - fn request(self: *FakeProbes, grant: Grant) void { + pub fn request(self: *FakeProbes, grant: Grant) void { self.requests[self.request_count] = grant; self.request_count += 1; } - fn nowMs(self: *FakeProbes) i64 { + pub fn nowMs(self: *FakeProbes) i64 { return self.now; } fn requested(self: *FakeProbes) []const Grant { diff --git a/src/insertion_runner.zig b/src/insertion_runner.zig index 4012689..5631557 100644 --- a/src/insertion_runner.zig +++ b/src/insertion_runner.zig @@ -455,11 +455,11 @@ const FakeDeps = struct { /// flight and its clipboard restore is still owed. quit_during_insert: bool = false, - fn insertionPlan(self: *FakeDeps) insertmod.Plan { + pub fn insertionPlan(self: *FakeDeps) insertmod.Plan { return self.plan; } - fn insert(self: *FakeDeps, plan: insertmod.Plan, text: [*:0]const u8) insertmod.InsertError!void { + pub fn insert(self: *FakeDeps, plan: insertmod.Plan, text: [*:0]const u8) insertmod.InsertError!void { if (self.quit_during_insert) self.quit = true; self.calls += 1; self.last_plan = plan; @@ -469,7 +469,7 @@ const FakeDeps = struct { return self.result; } - fn complete(self: *FakeDeps, id: coord.UtteranceId, result: coord.InsertResult, focused_app: ?coord.AppIdentity, inserted: []const u8) void { + pub fn complete(self: *FakeDeps, id: coord.UtteranceId, result: coord.InsertResult, focused_app: ?coord.AppIdentity, inserted: []const u8) void { self.completions += 1; self.last_completion_id = id; self.last_completion = result; @@ -478,17 +478,17 @@ const FakeDeps = struct { self.last_inserted_len = inserted.len; } - fn focusedApp(self: *FakeDeps) ?coord.AppIdentity { + pub fn focusedApp(self: *FakeDeps) ?coord.AppIdentity { self.focus_reads += 1; return self.focused_app; } - fn finishInsert(self: *FakeDeps) void { + pub fn finishInsert(self: *FakeDeps) void { self.finishes += 1; self.completions_at_finish = self.completions; } - fn copyToClipboard(self: *FakeDeps, text: [*:0]const u8) void { + pub fn copyToClipboard(self: *FakeDeps, text: [*:0]const u8) void { self.copies += 1; self.finishes_at_copy = self.finishes; const s = std.mem.span(text); @@ -496,15 +496,15 @@ const FakeDeps = struct { self.last_copy_len = s.len; } - fn actionRefused(self: *FakeDeps) void { + pub fn actionRefused(self: *FakeDeps) void { self.refuses += 1; } - fn shouldQuit(self: *FakeDeps) bool { + pub fn shouldQuit(self: *FakeDeps) bool { return self.quit; } - fn idle(self: *FakeDeps) void { + pub fn idle(self: *FakeDeps) void { self.idles += 1; self.quit = true; } diff --git a/src/local_backend.zig b/src/local_backend.zig index fbc3e69..ee8eac6 100644 --- a/src/local_backend.zig +++ b/src/local_backend.zig @@ -548,18 +548,18 @@ const FakeHelper = struct { reserve_error: bool = false, submit_error: bool = false, - fn isReady(_: *FakeHelper) bool { + pub fn isReady(_: *FakeHelper) bool { return true; } - fn setEvents(_: *FakeHelper, _: HelperEvents) void {} - fn reserveUtterance(self: *FakeHelper, id: backend.UtteranceId) !void { + pub fn setEvents(_: *FakeHelper, _: HelperEvents) void {} + pub fn reserveUtterance(self: *FakeHelper, id: backend.UtteranceId) !void { if (self.reserve_error) return error.NotReady; if (self.lease_id != null) return error.Busy; self.lease_id = id; self.reserves += 1; } - fn submit(self: *FakeHelper, id: backend.UtteranceId, language: ipc.Language, prompt: []const u8, pcm: []const u8) !void { + pub fn submit(self: *FakeHelper, id: backend.UtteranceId, language: ipc.Language, prompt: []const u8, pcm: []const u8) !void { if (self.submit_error) return error.BrokenPipe; if (self.lease_id != id) return error.WrongUtterance; self.ids[self.submits] = id; @@ -572,25 +572,25 @@ const FakeHelper = struct { self.last_id = id; self.language = language; } - fn cancel(self: *FakeHelper, id: backend.UtteranceId) void { + pub fn cancel(self: *FakeHelper, id: backend.UtteranceId) void { self.cancels += 1; self.last_cancel_id = id; if (self.lease_id == id) self.lease_id = null; } - fn requestCancel(self: *FakeHelper, id: backend.UtteranceId) void { + pub fn requestCancel(self: *FakeHelper, id: backend.UtteranceId) void { self.cancellation_requests += 1; self.last_request_id = id; } - fn retry(self: *FakeHelper) void { + pub fn retry(self: *FakeHelper) void { self.retries += 1; } // The Adapter's segmentation tests never drive backend reselection or teardown, so these // two members of the Helper contract are inert here — present so the fake is a complete // Helper (asserted below), not a partial one. - fn usesModel(_: *FakeHelper, _: []const u8) bool { + pub fn usesModel(_: *FakeHelper, _: []const u8) bool { return true; } - fn shutdown(_: *FakeHelper) void {} + pub fn shutdown(_: *FakeHelper) void {} }; comptime { diff --git a/src/session.zig b/src/session.zig index dc579e6..1bb0df8 100644 --- a/src/session.zig +++ b/src/session.zig @@ -1440,40 +1440,40 @@ const FakeTransport = struct { write_buf: [1 << 14]u8 = undefined, write_len: usize = 0, - const Reader = struct { + pub const Reader = struct { pub fn join(_: Reader) void {} }; - fn init(_: std.Io, _: std.mem.Allocator) FakeTransport { + pub fn init(_: std.Io, _: std.mem.Allocator) FakeTransport { return .{}; } - fn connect(self: *FakeTransport, _: []const u8) !void { + pub fn connect(self: *FakeTransport, _: []const u8) !void { self.connected = true; } - fn startReadLoop(self: *FakeTransport, _: anytype) !Reader { + pub fn startReadLoop(self: *FakeTransport, _: anytype) !Reader { self.reads_started += 1; return .{}; } - fn write(self: *FakeTransport, bytes: []u8) !void { + pub fn write(self: *FakeTransport, bytes: []u8) !void { self.writes += 1; if (self.write_len + bytes.len <= self.write_buf.len) { @memcpy(self.write_buf[self.write_len..][0..bytes.len], bytes); self.write_len += bytes.len; } } - fn writePing(self: *FakeTransport, _: []u8) !void { + pub fn writePing(self: *FakeTransport, _: []u8) !void { self.pings += 1; } - fn writePong(self: *FakeTransport, _: []u8) !void { + pub fn writePong(self: *FakeTransport, _: []u8) !void { self.pongs += 1; } - fn writeCloseFrame(self: *FakeTransport, _: []u8) !void { + pub fn writeCloseFrame(self: *FakeTransport, _: []u8) !void { self.close_frames += 1; } - fn forceClose(self: *FakeTransport) void { + pub fn forceClose(self: *FakeTransport) void { self.force_closes += 1; } - fn deinit(self: *FakeTransport) void { + pub fn deinit(self: *FakeTransport) void { self.connected = false; } diff --git a/src/undo.zig b/src/undo.zig index 24f62f2..982f395 100644 --- a/src/undo.zig +++ b/src/undo.zig @@ -375,22 +375,22 @@ const FakeDeps = struct { undo_confirms: usize = 0, undo_refuses: usize = 0, - fn enabled(self: *FakeDeps) bool { + pub fn enabled(self: *FakeDeps) bool { self.enabled_reads += 1; return self.on; } - fn focusedApp(self: *FakeDeps) ?coord.AppIdentity { + pub fn focusedApp(self: *FakeDeps) ?coord.AppIdentity { self.focus_reads += 1; if (self.focus_switch_after) |k| { if (self.focus_reads > k) return self.focused_app_later; } return self.focused_app; } - fn secureInputActive(self: *FakeDeps) bool { + pub fn secureInputActive(self: *FakeDeps) bool { self.secure_reads += 1; return self.secure_input; } - fn deleteChars(self: *FakeDeps, n: usize) usize { + pub fn deleteChars(self: *FakeDeps, n: usize) usize { self.deletes += 1; self.last_delete_n = n; const available = if (self.delete_posts) |budget| budget -| self.posted_total else n; @@ -398,10 +398,10 @@ const FakeDeps = struct { self.posted_total += got; return got; } - fn undoConfirmed(self: *FakeDeps) void { + pub fn undoConfirmed(self: *FakeDeps) void { self.undo_confirms += 1; } - fn undoRefused(self: *FakeDeps) void { + pub fn undoRefused(self: *FakeDeps) void { self.undo_refuses += 1; } };