⏺ Stage 1 is complete and green. Summary of this PR (behavior-preserving #16
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "staged-pr-1-span"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
refactor — no runtime behavior change):
Stage 1: LGoal = Spanned
Spanned, reusing plg_shared::Spanned. Every goal carries a span
uniformly — the span moved off the Call variant onto the wrapper, and
codegen reads goal.span at the existence-error site.
flatten_conj/lower_body now share a splice_lowered helper), body.rs
(compile_seq matches &goal.node), and clause.rs (emit_inline_builtin
matches &g.node).
"defined-but-unused" note from PR#14's review). SPANS.md's fast-follow note
updated — the wrapper is done; only the append_to_error_msg helper remains
as in-line work for the next stage.
Verification: 399 tests pass, clippy -D warnings clean, and crucially
golden IR is byte-identical + existence_error provenance still works —
confirming it's a pure refactor. The payoff: adding a raising kind now
costs zero span plumbing (just read g.span), and finer granularity later is
a parser change, not an IR one.
Ready for you to branch/PR. Stage 2 (next PR) wires arithmetic provenance
on top of this: site_id through plg_rt_b_is + plg_rt_b_arith_cmp, the
runtime error constructors (evaluation/type_error) gain a site_id param,
codegen passes goal.span at those call sites (spans are already there via
the wrapper), plus the append_to_error_msg helper. Then later stages: the
type-checking det builtins, and the diff-test suffix stripping.
Review — Stage 1:
LGoal = Spanned<LGoalKind>Small, focused, behavior-preserving refactor. Right call to do this before wiring arithmetic provenance — the next-stage diff becomes "read
g.spanat the emit site" instead of "threadspaninto seven variants and update every constructor." The fact that golden IR comes out byte-identical is the test result that matters; it's load-bearing evidence that the refactor is purely structural.What's right
spanonSpanned<LGoalKind>rather thanLGoalKind::Callis the maintainable shape: each new raising kind readsg.spanwith zero plumbing, and the type system stops being something to re-thread for each addition.splice_lowereddeduplicates the conj-splicing logic. The drop-True+ flatten-Conj+ push-default branch existed in bothflatten_conjandlower_body; one helper now owns it. If a future kind wants to be spliced (e.g.Disjof one element collapses), there's now one place to teach.Spanned<T>doc updated. Closes the dangling "no in-tree consumer" note from PR #14's review. The new comment names both consumers (parser body conjuncts, IR goals) instead of leaving it as "reserved for Layer 3."append_to_error_msgremains" — let a future contributor pick up the slack without rereading the whole doc.LGoalKind::Disj,IfThen,IfThenElse,Naf,Onceeach takeBox<LGoal> = Box<Spanned<LGoalKind>>, so each branch already carries its leaf span. Stage 2 + later raising kinds (type_error in arithmetic, evaluation_error inis/2) get precise leaf provenance for free.Small observations
1.
splice_lowereddrops the outerConjwrapper's span. When(b, c)lowers toSpanned<Conj([Spanned<b>, Spanned<c>])>, the outer span (covering the parenthesized expression) is silently discarded as we extend withinner. Today this is a no-op:flatten_conjpropagates the samespanto every inner leaf via the recursion, so the inner goals already carry the parent's span. But the wrapper span itself is gone — if a future feature wants "this Conj appeared at file:line:col" for, say, nested-goal stack traces, that information is no longer reachable from the spliced output. Worth a one-line comment insplice_lowereddocumenting "we drop the wrapper span; per-leaf spans (set byflatten_conj's propagation) are the source of truth after splicing."2. No regression test asserts "every lowered goal has a non-default span." The type system guarantees the field exists, but a
Span::point(0, 0)slipping in from a construction path that forgetsspanwould compile clean. A small lowering test that walksVec<LGoal>for a representative body and asserts eachg.span != Span::point(0, 0)(orspan.hi > span.lofor non-empty inputs) would pin the contract — useful when Stage 2 starts usingg.spanin tight loops. Not a blocker; the existence_error provenance test from PR #15 indirectly coversCall.3.
splice_lowered'sSpanned::new(kind, lowered.span)reconstruction is the cost of partial-movinglowered.nodein the match. Acceptable as written. A destructure-first form would slightly cleaner:Reads as "split the wrapper, decide what to do with the kind." Stylistic; same compiled output.
4.
goals_ofnow uses_instead ofotheras the wildcard. Old code:other => vec![other.clone()]. New:_ => vec![g.clone()]. Equivalent behavior — the wildcard is fine since we already havegin scope — but the prior form was slightly more self-documenting ("the non-Conj arm"). Trivial.What's not in scope (and shouldn't be)
The
append_to_error_msg(&mut self, &str)helper is the right thing to land alongside Stage 2 (arithmetic provenance) rather than here — it only pays off once a hot raising path exists. Same for the lexer-span loss carried over from PR #14: out of scope for the IR refactor.Net
Pure structural cleanup that pays for itself the moment Stage 2 wires
g.spanintoplg_rt_b_isandplg_rt_b_arith_cmp. Ready to merge as-is; the wrapper-span-drop note (#1) is the only thing I'd want documented before moving on.