EmitLiteral: render data configs as Rust source - #760
Closed
0-jake-0 wants to merge 1 commit into
Closed
Conversation
The other half of what a two-pass generator needs from a wired graph, and
the gap the emission spike pinned. `quote` recovers *closure* configs
because closures are erased and only their tokens survive; this covers the
*data* configs — a ticker's `Duration`, a fold's seed, a limit's bound —
which are not erased at all, they just have no way to say what they would
look like written down.
Render the value, not its name. A `lit!(PERIOD)` capturing the token would
read better in an artifact, but a name only resolves if the artifact is
compiled where that name is bound — and the whole point of pass 1 is
topology decided by running code, where the values come from a config file
and have no name at all. Rendering serves both, which is why §3 specifies
it, and the same mechanism will serve §3's frozen captures.
Impls cover §3's named set — integers (suffixed, so an unsuffixed `1` in
an artifact cannot infer to `i32` against a `u64` config), floats, bool,
char, `str`/`String`, `Duration`, `NanoTime`, `Option`, `Vec`, slices,
tuples to 4. Every rendered path is absolute, because a generated file is
compiled in a scope the generator does not control.
Two details worth the reading:
- Floats use `{:?}`, not `{}` — `Debug` is the formatting *specified* to
round-trip. Non-finite values have no literal form and are named
(`::core::primitive::f64::INFINITY`).
- `Duration::new(secs, nanos)` rather than `from_millis` and friends,
which only round-trip durations landing on their unit.
`tests/emit_literal.rs` proves the round-trip the way the emission spike
proves wiring validity: each expected literal is *also written out as real
source* in the test, so the file compiling proves the output parses and
the equality proves it reconstructs. Neither assertion alone would.
Wired into the graph via `Stream::with_cfg` + `NodeInfo::cfg_src`, so the
walker in `tests/emission_spike.rs` no longer needs a caller-supplied
table — and the artifact it emits is now fully self-contained, carrying
`Duration::new(0, 1_000_000)` instead of depending on a `PERIOD` constant
being in scope at the far end. That closes gap 1 of the three the spike
recorded; a test now pins the self-containment.
The cost is the one §3 names loudly and this records again: emitting a
value **freezes** it. The artifact carries pass 1's configuration, so
changing it means regenerate and recompile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4eojqRWBJ6uK5v5JDzmkd
This was referenced Aug 8, 2026
Contributor
Author
|
Superseded by #769, which squashes this whole stack (#759–#768) onto
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #759 — base is
closure-quotation, notmain. Review that one first; this diff is only theEmitLiteralcommit on top.What this changes
Values can now render themselves as Rust source that reconstructs them, and a node can record its data config alongside its closure source. The emission walker in
tests/emission_spike.rsconsequently produces a fully self-contained artifact — one that compiles in a scope the generator does not control.Why
The other half of what a two-pass generator needs from a wired graph, and gap 1 of the three #759's spike pinned.
quoterecovers closure configs because closures are erased and only their tokens survive. This covers the data configs — aticker'sDuration, afold's seed, alimit's bound — which are not erased at all; they just have no way to say what they would look like written down.Render the value, not its name. A
lit!(PERIOD)capturing the token reads better in an artifact, and I nearly built it. It is wrong for the case the generator exists to serve: a name only resolves if the artifact is compiled where that name is bound, and the whole point of pass 1 is topology decided by running code — periods from a config file, thresholds from a database — where the values have no name at all. Rendering serves both, which is why §3 specifies it, and the same mechanism will serve §3's frozen captures ({ let thresh = 0.25f64; move |x| … }).Impls cover §3's named set: integers (suffixed, so an unsuffixed
1cannot infer toi32against au64config), floats,bool,char,str/String,Duration,NanoTime,Option,Vec, slices, tuples to 4. Every rendered path is absolute.How it was verified
cargo fmt --allcargo lint✅ —cargo lint-allnot run:--all-featurescannot build here (aeron needs CMake ≥3.30, box has 3.28; etcd needs protoc). Documented prerequisites, unrelated to this diff; CI is the first real check on that feature set.--all-featuresblocked as above. Full default suite plusbench,async,csv,cache,augurs,market,ws,redis,postgres,kafka,web,kdb,prometheus,fix,dynamic-graph,tracing— green. Doctests green (10 passed).tests/emit_literal.rsproves the round-trip the way #759's spike proves wiring validity: each expected literal is also written out as real source in the test, so the file compiling proves the output parses and the equality proves it reconstructs. Neither assertion alone would — a string can be exactly what you expected and still not be valid Rust, or be valid Rust evaluating to something else.Notes for the reviewer
Two formatting details are load-bearing, not stylistic:
{:?}, not{}—Debugis the formatting specified to round-trip.0.1 + 0.2renders as0.30000000000000004f64, andf64::MIN_POSITIVEsurvives. Non-finite values have no literal form at all and are named (::core::primitive::f64::INFINITY).Duration::new(secs, nanos)rather thanfrom_millisand friends, which only round-trip durations landing on their unit.Duration::new(2, 500_000_001)is the case that motivated it.Absolute paths everywhere, guarded by a test. An impl relying on
usestatements being present at the far end would produce source that compiles in this crate and fails in a generated artifact — a failure mode that would surface only at pass 2, in code nobody wrote.This closes gap 1 of #759's three. The walker no longer needs a caller-supplied config table, and a new test pins that the artifact contains no reference to the generator's own constants. Gaps 2 (an unquoted closure is indistinguishable from no closure) and 3 (multi-edge emission unproven) are unchanged and still pinned there.
The cost, recorded in the module docs and again here: emitting a value freezes it. The artifact carries pass 1's configuration, so changing it means regenerate and recompile. That is partial evaluation and often the point — but it is the thing that ships a stale threshold if nobody says it loudly.
Generated by Claude Code