Files
AILang/docs/journals/2026-05-14-iter-bugfix-mono-cursor-print-with-class-method-arg.md
T
Brummel 1fb225ee25 bugfix: mono cursor misalignment at poly-free-fn Var with class-constrained Forall
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.
2026-05-14 01:38:41 +02:00

4.3 KiB
Raw Blame History

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_cursor with 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_module helper immediately after poly_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> into collect_residuals_ordered, interleave_slots, and rewrite_mono_calls (signatures + every recursive call site).
    • interleave_slots::Term::Var poly-free-fn branch: after pushing the FreeFn slot, drain N entries from class_slots and push them as fillers in out, advancing class_cur by N.
    • rewrite_mono_calls::Term::Var: compute n_filler from the count map before the rename mutation, then *cursor += 1 + n_filler. Class-method Vars are guarded with if is_poly_free_fn { … } else { 0 }.
    • RED print_with_class_method_arg_does_not_misalign_mono_cursor flips to GREEN with stdout false as predicted by the fixture docstring.
    • Full cargo test --workspace passes 564 / 564 (= 563 baseline from clippy-sweep + the 1 new mini-mode RED). Zero regressions across any of the existing show / mono / typeclass test suites.

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 pin print_with_class_method_arg_does_not_misalign_mono_cursor (committed by /debug upstream of this iter; flips RED → GREEN here).
  • examples/print_eq_arg_repro.ail — RED fixture (committed by /debug upstream; consumed by the pin).

Stats

bench/orchestrator-stats/2026-05-14-iter-bugfix-mono-cursor-print-with-class-method-arg.json