(module loop_recur_str_binder_no_leak_pin ; RED for bug #49 (Str leg): a heap-Str loop binder that `recur` ; REPLACES with a fresh heap value each iteration. The seed is the ; static literal "x"; each `recur` rebinds `acc` to a FRESH heap slab ; returned by `str_concat`. Codegen's Term::Recur arm ; (crates/ailang-codegen/src/lib.rs) decs the superseded prior binder ; value only under the gate `is_ptr && !is_str`; the `!is_str` clause ; excludes a Str binder entirely, so the plain `store` overwrites the ; prior str_concat slab without RC-dec'ing it, leaking it every ; iteration. ; ; The companion ADT fixture (loop_recur_heap_binder_no_leak_pin.ail, ; commit f488d31) is the SAME leak class WITH the dec emitted; the only ; difference is the `!is_str` clause. The Str exclusion was a ; conservatism about the static seed literal: the seed "x" may be a ; constexpr-GEP into a static global with no rc_header, so an ; unconditional dec on iteration 1 would be UB. But the intermediate ; str_concat results ARE heap slabs that must be dec'd. ; ; allocs: three str_concat results (acc="xy","xyy","xyyy") = 3 heap ; slabs (the seed "x" is a static literal, not counted as an alloc). ; The loop returns acc="xyyy"; that FINAL result is consumed by print. ; The two superseded heap results ("xy", "xyy") plus the final "xyyy" ; after print must all be freed. Pre-fix: live=3. Post-fix: live=0. ; Expected stdout: xyyy. (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let s (loop (acc (con Str) "x") (i (con Int) 0) (if (app ge i 3) acc (recur (app str_concat acc "y") (app + i 1)))) (app print s)))))