Iter 15g-aux — symmetric $u early-return in unify_for_subst

Fix the codegen asymmetry that 15g surfaced. unify_for_subst had an
arg-side-only early-return for $u-prefixed synth wildcards. The
function's prev-binding recursion can swap a $u from arg into param
position when re-unifying a previously-bound type against a fresh
arg. Reduced repro: `length [Left 1, Right 10]` — `a` first binds
to `Either<Int, $u>`, then the recursive unification against
`Either<$u, Int>` lands $u in param-pos[1] and falls through to
the catch-all error.

Fix is three lines: add a symmetric $u early-return for param side.
$u is a synth-only wildcard regardless of which side it ends up on
after the prev-binding swap. Doc comment expanded to record the
origin and justification.

std_either_list_demo refactored: mkleft/mkright workaround helpers
removed; the list is now constructed inline by mixing
(term-ctor std_either.Either Left 1) and (... Right 10) directly.
Same expected output (2, 3, 2, 3); the demo doubles as the 15g-aux
regression fixture.

Tests: 94/94, unchanged. The fix expanded what compiles without
changing observable behaviour for any prior fixture.
This commit is contained in:
2026-05-07 19:59:42 +02:00
parent e1587ebdae
commit de674c4d0b
4 changed files with 92 additions and 39 deletions
File diff suppressed because one or more lines are too long
+15 -31
View File
@@ -1,20 +1,16 @@
; Iter 15g — fifth consumer demo. First demo to import four stdlib
; modules (std_list, std_either, std_pair, std_either_list); first to
; thread a List<Either<Int, Int>> through three combinators that
; Iter 15g (refactored in 15g-aux) — fifth consumer demo. Imports four
; stdlib modules (std_list, std_either, std_pair, std_either_list);
; threads a List<Either<Int, Int>> through three combinators that
; together exercise every monomorphisation site introduced by 15g.
;
; The list is constructed via the monomorphic helpers `mkleft` and
; `mkright` rather than bare `(term-ctor std_either.Either Left ...)` /
; `(term-ctor std_either.Either Right ...)`. This sidesteps a codegen
; bug that 15g surfaced: when an inline list literal mixes
; `Either Left n` (pins e, leaves a as `$u`) and `Either Right n` (pins
; a, leaves e as `$u`), the synth-time unification of the outer
; `List<a>`'s parameter against the `$u`-bearing element types fails
; on the param side because `unify_for_subst` only accepts arg-side
; `$u` wildcards. The helpers fully pin both type vars at the call
; site, so each list element arrives with concrete `Either<Int, Int>`
; and the bug is dodged. Logged as known-debt under 15g; the fix is
; a one-line symmetric extension of the `$u` early-return.
; The list is constructed inline by mixing `(term-ctor std_either.Either
; Left ...)` and `(... Right ...)`. Iter 15g surfaced a `unify_for_subst`
; asymmetry that would have rejected this construction at synth time;
; 15g-aux fixed it by accepting `$u` synth-wildcards on either side of
; the param/arg unification (see ailang-codegen/src/lib.rs's
; `unify_for_subst`). This demo is the regression fixture: if the
; symmetric early-return is reverted, the inline form below errors out
; before reaching codegen.
(module std_either_list_demo
@@ -23,24 +19,12 @@
(import std_pair)
(import std_either_list)
(fn mkleft
(doc "Monomorphic Left-injector at Either<Int, Int>. Pins both type vars at the call site so the codegen synth doesn't leave a `$u` wildcard on the second var.")
(type (fn-type (params (con Int)) (ret (con std_either.Either (con Int) (con Int)))))
(params x)
(body (term-ctor std_either.Either Left x)))
(fn mkright
(doc "Monomorphic Right-injector at Either<Int, Int>. Symmetric to mkleft.")
(type (fn-type (params (con Int)) (ret (con std_either.Either (con Int) (con Int)))))
(params x)
(body (term-ctor std_either.Either Right x)))
(fn main
(doc "Drive lefts, rights, and partition_eithers on the same five-element List<Either<Int, Int>> (Left 1, Right 10, Left 2, Right 20, Right 30). Expected output (one per line): 2, 3, 2, 3.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(seq (do io/print_int (app std_list.length (app std_either_list.lefts (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil)))))))))
(seq (do io/print_int (app std_list.length (app std_either_list.rights (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil)))))))))
(seq (do io/print_int (app std_list.length (app std_pair.fst (app std_either_list.partition_eithers (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil))))))))))
(do io/print_int (app std_list.length (app std_pair.snd (app std_either_list.partition_eithers (term-ctor std_list.List Cons (app mkleft 1) (term-ctor std_list.List Cons (app mkright 10) (term-ctor std_list.List Cons (app mkleft 2) (term-ctor std_list.List Cons (app mkright 20) (term-ctor std_list.List Cons (app mkright 30) (term-ctor std_list.List Nil))))))))))))))))
(seq (do io/print_int (app std_list.length (app std_either_list.lefts (term-ctor std_list.List Cons (term-ctor std_either.Either Left 1) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 10) (term-ctor std_list.List Cons (term-ctor std_either.Either Left 2) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 20) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 30) (term-ctor std_list.List Nil)))))))))
(seq (do io/print_int (app std_list.length (app std_either_list.rights (term-ctor std_list.List Cons (term-ctor std_either.Either Left 1) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 10) (term-ctor std_list.List Cons (term-ctor std_either.Either Left 2) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 20) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 30) (term-ctor std_list.List Nil)))))))))
(seq (do io/print_int (app std_list.length (app std_pair.fst (app std_either_list.partition_eithers (term-ctor std_list.List Cons (term-ctor std_either.Either Left 1) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 10) (term-ctor std_list.List Cons (term-ctor std_either.Either Left 2) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 20) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 30) (term-ctor std_list.List Nil))))))))))
(do io/print_int (app std_list.length (app std_pair.snd (app std_either_list.partition_eithers (term-ctor std_list.List Cons (term-ctor std_either.Either Left 1) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 10) (term-ctor std_list.List Cons (term-ctor std_either.Either Left 2) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 20) (term-ctor std_list.List Cons (term-ctor std_either.Either Right 30) (term-ctor std_list.List Nil))))))))))))))))