Files
AILang/examples
Brummel ba63a16366 Iter 18d.4: pattern-binder + Own-param dec at scope close
Closes the memory-hygiene regression 18d.3 introduced and the
symmetric Own-param-leaks debt 18c.3 / 18c.4 left open. Both
share an emission shape — a binder going out of scope without
being consumed (consume_count == 0) gets a drop call emitted.

Two emission seams added to crates/ailang-codegen/src/lib.rs:

(A) Pattern-binder dec at arm body close (lower_match): after
each arm's body lowers, for each pattern-bound binder of this
arm with consume_count == 0 and ptr-typed value, emit drop.
Routes through field_drop_call when moved_slots[binder] is
empty (the common case — pattern-bound binders rarely have
their own moves recorded), falls back to shallow ailang_rc_dec
when moved_slots is non-empty (dynamic-tag partial-drop debt).
Wildcard slots are NOT dec'd here — they're the source's
responsibility, which 18d.3 already handles via emit_inlined_
partial_drop.

(B) Own-param dec at fn return (emit_fn): widened the fn-type
destructure to pull param_modes. After the body's tail value
lowers but before the ret instruction, for each fn parameter
with param_modes[i] == Own and consume_count == 0 and ptr-typed
value AND not the body's tail SSA, emit drop. Same routing as
(A). Borrow/Implicit params are explicitly excluded — Borrow
because the caller still owns, Implicit because there's no
static "caller handed off" signal under back-compat.

drop_<m>_<T> itself remains uniform-on-fully-owned. The partial-
drop complexity is at the call sites with the static info,
matching the design call from 18c.4 and 18d.3.

Tests:
- examples/rc_own_param_drop.{ailx,ail.json} — canonical Own-
  param fixture.
- alloc_rc_own_param_dec_at_fn_return E2E asserts (B)'s drop
  fires before ret in the fn body's IR.
- alloc_rc_borrow_only_recursive_list_drop expanded with IR-
  shape assertion that t's drop fires at arm close (closes the
  18d.3 regression).

Side effect (correctness improvement): sum_list in reuse_as_
demo declares (own (con List)) and has consume_count(xs) == 0;
(B) now fires shallow ailang_rc_dec(xs) at each recursive
return, so the chain frees one cell per stack frame on unwind.
Stdout unchanged (9); existing reuse-arm IR-shape assertions on
map_inc continue to hold (they don't see sum_list's body).

Test deltas: e2e 57 -> 58. All other buckets unchanged. cargo
test --workspace green.

Known debt (deliberate, deferred):
- Dynamic-tag partial-drop. emit_inlined_partial_drop requires
  a static Term::Ctor for layout; binders with non-empty
  moved_slots whose runtime tag is dynamic fall back to shallow
  ailang_rc_dec of the outer cell. Sound for shipping fixtures
  (the moved-out child has been dec'd elsewhere by the time
  iter B fires; remaining ctors have no further pointer fields
  to dec). General fix needs tag-switch in the dec emission —
  separate iter.
- Closure-typed Own params still go through field_drop_call's
  Type::Fn arm (shallow dec), same path 18c.4 carved out.
- (drop-iterative) worklist allocator — Iter 18e.
2026-05-08 12:33:48 +02:00
..
2026-05-07 10:38:07 +02:00