P6.x: sweep emitter/wrangler.rs off bynk_syntax::ast (slice 2 of #1187) - #1192
Conversation
emit_wrangler_toml's two raw matches on a handler's cron kind and a service's queue-binding protocol relocate to its one call site in project.rs, which already imports bynk_syntax::ast and is already counted by the ast_importers probe. crons/queues now arrive as pre-collected, sorted+deduped &[String] parameters instead of being walked here off table.services -- that was this file's entire raw- syntax footprint, so relocating it drops wrangler.rs out of the probe outright. No bynk-emit::ir equivalent exists to route through instead: no project-wide IrItem::Service is built at the call site, and IrHandler::kind reuses HandlerKind unchanged even where one is, so an IR-based version would still have matched the cron kind directly. runtime_use.rs, originally paired with wrangler.rs in #1187's own table as a joint trivial sweep, is deliberately untouched here -- correcting that table (#1191, comment on #1187). Its TypeRef field is downstream of emitter/serialisation.rs's still-AST-driven JSON-codec renderer (serialise_expr/deserialise_expr/ts_type_ref_qualified), a real, separate, unscoped conversion, not a relocation like this one. ast_importers moves 9 to 8 (design/greenfield-status.md regenerated). No author-facing behaviour change; existing cron/queue wrangler.toml fixtures (146-150, 154, 147, 393, 372) stay byte-identical. Closes #1191. Part of #1137; realises slice 2 (narrowed to wrangler.rs only) of #1187. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
| // v0.10a: every `on cron "expr"` schedule in the context, sorted+deduped. | ||
| crons: &[String], | ||
| // v0.10b/v0.44: every `from queue("name")` service's bound queue name, | ||
| // sorted+deduped. | ||
| queues: &[String], |
There was a problem hiding this comment.
The relocation turns a self-contained invariant into an unenforced cross-file precondition, and the comment records what it is but not why it's load-bearing.
table.services is a HashMap<String, ServiceDecl> (bynk-check/src/symbols.rs:306), so iteration order is not stable across runs — the sort() is what makes wrangler.toml reproducible, not just tidy. While it lived here it was structurally impossible to lose; now a caller that forgets it produces a nondeterministic config with no compile error.
Worth spelling that out so the next person doesn't read "sorted+deduped" as cosmetic:
| // v0.10a: every `on cron "expr"` schedule in the context, sorted+deduped. | |
| crons: &[String], | |
| // v0.10b/v0.44: every `from queue("name")` service's bound queue name, | |
| // sorted+deduped. | |
| queues: &[String], | |
| // v0.10a: every `on cron "expr"` schedule in the context, sorted+deduped. | |
| // Sorting is load-bearing, not cosmetic: the caller walks `table.services`, | |
| // a `HashMap`, so unsorted input makes `wrangler.toml` non-reproducible. | |
| crons: &[String], | |
| // v0.10b/v0.44: every `from queue("name")` service's bound queue name, | |
| // sorted+deduped (same reproducibility requirement as `crons`). | |
| queues: &[String], |
Also note this leaves three adjacent string-slice parameters (consumes, crons, queues) — any future transposition at the call site type-checks silently.
| crons.sort(); | ||
| crons.dedup(); | ||
| queues.sort(); | ||
| queues.dedup(); |
There was a problem hiding this comment.
Test coverage: the sort/dedup that just crossed a file boundary is not discriminatingly exercised by any fixture, so a regression here would land silently.
Checked the fixtures the PR description cites:
148_cron_multiple— both crons live in one service'shandlersVec(ordered), so it can't detect a droppedcrons.sort()at all; and"*/5 * * * *"<"0 0 * * *"already, so declaration order equals sorted order.153_queue_multiple—high-priority/low-prioritycome from twoHashMapentries, so it would catch a droppedqueues.sort(), but only as an intermittent failure, and the names are alphabetical by declaration anyway.154_full_jobs,146,147,149,150,393,372— single cron / single queue each.- Nothing covers
dedup()in either direction: no fixture has the same cron expression on two services in one context. (negative/118_queue_duplicate_consumerrejects duplicate queue names upstream, soqueues.dedup()looks purely defensive — fine, but say so rather than leaving it as apparent live logic.)
This gap predates the PR — while the code sat inside emit_wrangler_toml it couldn't be wrong. It becomes worth closing now precisely because the invariant is a caller obligation. A cheap fix is one fixture with two services in one context sharing a cron expression and declared in reverse-sorted order — that pins sort and dedup for crons in a single expected wrangler.toml. A direct unit test on emit_wrangler_toml would work too, though it'd assert the contract from the wrong side of it.
|
Review — sound relocation, two follow-ups Verified the core claim directly: Findings
Release discipline Correct: exactly one increment, One observation, not a blocker The regenerated table also moves the non-gated trend rows: Neither finding blocks the merge — the relocation itself is faithful. |
…vice cron fixture
Two findings from the automated review, both coverage/hardening (no
correctness defect found):
1. emit_wrangler_toml's crons/queues parameters carried a bare
"sorted+deduped" comment with no reason -- while the sort lived
inside the function it was structurally guaranteed; as a caller
obligation across a file boundary it's now a silent failure mode
if a future caller forgets it. Spelled out why: table.services is
a HashMap, so the sort is what makes wrangler.toml reproducible
across runs, not cosmetic.
2. No fixture discriminated the sort/dedup that just crossed into
project.rs -- existing cron/queue fixtures each have a single
service or an already-sorted pair, so a dropped crons.sort()/
queues.sort() wouldn't necessarily be caught. Added
1191_cron_sort_dedup_across_services: two services in one context
with distinct cron schedules declared in reverse-sorted order.
Verified by hand that disabling crons.sort() makes this fixture
fail intermittently (HashMap iteration order isn't fixed), the
same probabilistic-but-real coverage 153_queue_multiple already
gives queues.sort().
The review's suggested fixture shape ("two services sharing a cron
expression") turns out not to be constructible as valid Bynk source:
bynk.cron.duplicate_schedule (context_checks.rs) already rejects a
duplicate cron schedule across all services in a context, the same
way bynk.queue.duplicate_consumer already rejects a duplicate queue
name -- so crons.dedup(), like queues.dedup(), is unreachable from
any positive fixture and is purely defensive. The added fixture
covers sort with two *distinct* schedules instead; dedup stays
untested by design, for the same reason queues.dedup() already was.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Both findings addressed in 68bc1f1:
One correction on the suggested shape: "two services sharing a cron expression" isn't constructible |
Summary
emit_wrangler_toml's two raw matches on a handler's cron kind and a service's queue-bindingprotocol relocate to its one call site in
project.rs, which already importsbynk_syntax::astand is already counted by the
ast_importersprobe.crons/queuesnow arrive as pre-collected,sorted+deduped
&[String]parameters instead of being walked here offtable.services— that wasthis file's entire raw-syntax footprint, so relocating it drops
wrangler.rsout of the probeoutright.
bynk-emit::irequivalent exists to route through instead: no project-wideIrItem::Serviceisbuilt at the call site, and
IrHandler::kindreusesHandlerKindunchanged even where one is, so anIR-based version would still have matched the cron kind directly.
emitter/runtime_use.rs, originally paired withwrangler.rsin P6.x candidate: cutover slicing — emitter.rs/emitter/lower.rs from bynk_syntax::ast to bynk-emit::ir #1187's own table as a jointtrivial sweep, is deliberately untouched here — corrected in P6.x: sweep emitter/wrangler.rs off bynk_syntax::ast (slice 2, narrowed — runtime_use.rs deferred) #1191 and via a comment on P6.x candidate: cutover slicing — emitter.rs/emitter/lower.rs from bynk_syntax::ast to bynk-emit::ir #1187. Its
TypeReffield is downstream ofemitter/serialisation.rs's still-AST-driven JSON-codec renderer(
serialise_expr/deserialise_expr/ts_type_ref_qualified), a real, separate, unscopedconversion, not a relocation like this one.
ast_importersmoves 9 → 8 (design/greenfield-status.mdregenerated). No author-facing behaviourchange.
Test plan
cargo build --workspace— cleancargo clippy --workspace --all-targets -- -D warnings— cleancargo fmt --all -- --check— cleancargo test --workspace— all crates passing, includingbynkc's fixture/e2e suite (cronfixtures
146-150/154/147, queue fixtures393/372— byte-identicalwrangler.tomloutput)
cargo xtask greenfield-status—ast_importersreads 8 (was 9); table regenerated with--applycargo xtask check-pending— 1 pending file validCloses #1191. Part of #1137; realises slice 2 (narrowed to
wrangler.rsonly) of #1187.🤖 Generated with Claude Code