Found while fixing #993, and deliberately left out of that change. Three residuals in the same region.
the compare answers wrong
fn f[T](t: T) -> Int:
s: String? := none
if s == none:
return 1
return 0
takes the else branch. The comparison falls through to a raw pointer equality against zero, and the shell's address is never zero — the same mechanism #987 fixed for a bare-none binding at the top level. It is a silent wrong answer.
It was not fixed with #993 because it routes through ir_generic_param_optional_tid rather than ir_emission_tid, and wiring the locals map into that path also drags in the ownership releases on it, which is a larger change than the bind fix.
only the annotated local is fixed
if let v = f() and s := f() inside a generic body still bind register zero. Recovering the shape there needs the callee's declared return type, which is a different recovery path — and really wants generic bodies to be type-checked at all, which is #927 stage 5.
the shell is never released
A generic body's optional shell has no owner: a specialization keeps no tuple local, so nothing releases it. Measured unchanged across #993 at 15.1 mb against 52.7 mb over the usual round counts.
Separately, a lambda body runs no reference-counting prologue at all, so it tracks and releases no locals: a plain s := "x" in a lambda grows 10.5 mb to 38.7 mb with no if let involved anywhere.
Both are pinned by failing twins in tests/pending/if_let_frame_ownership.pith rather than in tests/leaks/, since a known-failing reproducer in the gate directory would just make the gate red.
All of these point the same way: generic and lambda bodies are emitted with information the checker never produced for them, and each symptom gets patched where it surfaces. #927 stage 5 is the change that removes the category.
Found while fixing #993, and deliberately left out of that change. Three residuals in the same region.
the compare answers wrong
takes the else branch. The comparison falls through to a raw pointer equality against zero, and the shell's address is never zero — the same mechanism #987 fixed for a bare-none binding at the top level. It is a silent wrong answer.
It was not fixed with #993 because it routes through
ir_generic_param_optional_tidrather thanir_emission_tid, and wiring the locals map into that path also drags in the ownership releases on it, which is a larger change than the bind fix.only the annotated local is fixed
if let v = f()ands := f()inside a generic body still bind register zero. Recovering the shape there needs the callee's declared return type, which is a different recovery path — and really wants generic bodies to be type-checked at all, which is #927 stage 5.the shell is never released
A generic body's optional shell has no owner: a specialization keeps no tuple local, so nothing releases it. Measured unchanged across #993 at 15.1 mb against 52.7 mb over the usual round counts.
Separately, a lambda body runs no reference-counting prologue at all, so it tracks and releases no locals: a plain
s := "x"in a lambda grows 10.5 mb to 38.7 mb with noif letinvolved anywhere.Both are pinned by failing twins in
tests/pending/if_let_frame_ownership.pithrather than intests/leaks/, since a known-failing reproducer in the gate directory would just make the gate red.All of these point the same way: generic and lambda bodies are emitted with information the checker never produced for them, and each symptom gets patched where it surfaces. #927 stage 5 is the change that removes the category.