When `synth` visits a `Term::Var v` where v is a polymorphic free fn whose `Type::Forall` carries class constraints (e.g. `print : forall a. Show a => ...`, `lt`/`le`/`gt`/`ge : forall a. Ord a => ...`), it pushes BOTH a class `ResidualConstraint` for each constraint AND a `FreeFnCall` observation at the same Var site. The mono walkers (`interleave_slots` collection-side and `rewrite_mono_calls` rewrite-side) consumed only ONE slot per such Var, leaving the class-residual cursor misaligned for every subsequent class-method call in the same body. The next `eq`/`compare` Var in the body then consumed the leftover `Show T` / `Ord T` slot and was mis-rewritten — codegen reported `call prelude.show__Bool arg type mismatch: expected i1, got i64` (or the Int dual). Fix: new `poly_free_fn_constraint_counts_for_module` registry in mono.rs (sibling to `poly_free_fn_names_for_module`) maps each synth-visible poly-free-fn name to its declared constraint count. Threaded into `collect_residuals_ordered`, `interleave_slots`, and `rewrite_mono_calls`. Both walkers now advance the cursor by 1 + N (FreeFn slot + N class-residual fillers) at every poly-free-fn Var. RED test `print_with_class_method_arg_does_not_misalign_mono_cursor` (crates/ail/tests/show_print_e2e.rs) + fixture examples/print_eq_arg_repro.ail pin the bug — body `(app print (app eq 1 2))` previously crashed at codegen, now prints "false". Bug surfaced 2026-05-14 during the rpe.1 BLOCKED orchestrator run; existed in latent form since iter 24.3 (the poly-free-fn-with-class- constraint synth shape was new there). No schema / codegen / DESIGN.md changes. cargo test --workspace: 564 / 0 / 3.
4.3 KiB
iter bugfix-mono-cursor-print-with-class-method-arg — (app print (app eq …)) no longer rewrites the inner class-method to a stale Show T symbol
Date: 2026-05-14
Started from: 05e4c04e3b
Status: DONE
Tasks completed: 1 of 1
Summary
The iter 24.3 synth Var-arm pushes BOTH a class ResidualConstraint
for each declared constraint AND a FreeFnCall observation at the
same poly-free-fn Var site (crates/ailang-check/src/lib.rs ≈ 2908–
2966). The two mono walkers in crates/ailang-check/src/mono.rs,
however, were each consuming exactly one slot per Var: at a
poly-free-fn Var they popped one free_fn_slot and zero
class_slots. Every constraint on a poly-free-fn-with-constraints
left a leftover class slot, so the next class-method Var in the
same body consumed the wrong slot — the residual that belonged to
print's Show T constraint got cursor-aligned with the inner eq
Var, and codegen reported call prelude.show__Bool arg type mismatch: expected i1, got i64.
The fix advances both walkers' cursors by 1 + N at a poly-free-fn
Var whose source Type::Forall carries N class constraints. A
new sibling helper poly_free_fn_constraint_counts_for_module
mirrors poly_free_fn_names_for_module's three-source spelling
contract (same-module / implicit-import bare / dot-qualified
alias.name) and produces a BTreeMap<String, usize> from synth-
visible name to declared-constraint count. The map is threaded into
the rewrite-phase loop alongside poly_free_fns and passed to both
rewrite_mono_calls and interleave_slots.
In interleave_slots, a poly-free-fn Var now pushes the FreeFn
slot first (this is what the rewrite walker reads for the rename),
then N filler entries drawn in order from class_slots (consumed
only to keep class_cur aligned). In rewrite_mono_calls, the
Var-arm captures n_filler from the count map BEFORE the rename
mutates name (lookup keys are the synth-visible spelling), reads
the slot at *cursor as before, then advances by 1 + n_filler.
Class-method Vars stay at advance-by-one (synth pushes exactly one
residual and no FreeFnCall).
Per-task notes
- task 1: write/verify the cursor-alignment fix.
- Reproduced RED at
print_with_class_method_arg_does_not_misalign_mono_cursorwith the exact failure mode stated in the carrier:call prelude.show__Bool arg type mismatch: expected i1, got i64. - Added
poly_free_fn_constraint_counts_for_modulehelper immediately afterpoly_free_fn_names_for_module; identical name-source contract, returning constraint counts instead of a presence set. - Threaded
poly_free_fn_ccounts: &BTreeMap<String, usize>intocollect_residuals_ordered,interleave_slots, andrewrite_mono_calls(signatures + every recursive call site). interleave_slots::Term::Varpoly-free-fn branch: after pushing theFreeFnslot, drainNentries fromclass_slotsand push them as fillers inout, advancingclass_curbyN.rewrite_mono_calls::Term::Var: computen_fillerfrom the count map before the rename mutation, then*cursor += 1 + n_filler. Class-methodVars are guarded withif is_poly_free_fn { … } else { 0 }.- RED
print_with_class_method_arg_does_not_misalign_mono_cursorflips to GREEN with stdoutfalseas predicted by the fixture docstring. - Full
cargo test --workspacepasses 564 / 564 (= 563 baseline fromclippy-sweep+ the 1 new mini-mode RED). Zero regressions across any of the existing show / mono / typeclass test suites.
- Reproduced RED at
Concerns
(none)
Known debt
(none — the constraint set is a single new sibling helper plus a new threaded argument; the two-walker symmetry remains the invariant the existing comments already document.)
Files touched
crates/ailang-check/src/mono.rs— the fix (helper + two walker signatures + Var-arm cursor advancement + recursive-call threading).crates/ail/tests/show_print_e2e.rs— RED pinprint_with_class_method_arg_does_not_misalign_mono_cursor(committed by/debugupstream of this iter; flips RED → GREEN here).examples/print_eq_arg_repro.ail— RED fixture (committed by/debugupstream; consumed by the pin).
Stats
bench/orchestrator-stats/2026-05-14-iter-bugfix-mono-cursor-print-with-class-method-arg.json