(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)))))
