spec: unique binder names per fn — A2b leg of RawBuf drop-leak (refs #43)

The last open leg of the #43 owned-heap drop-leak cluster is the
UniquenessTable shadow-name collapse: the side-table keys by
(def_name, binder_name), so a fn that shadows a binder name collapses
every shadow onto one key. The outermost binding (records last, on pop)
overwrites the inner ones; codegen's scope-close drop gates then read
the collapsed consume_count for the innermost binding and suppress its
drop, leaking the owned slab.

Spec 0056 resolves this as a compiler-internal naming collision — NOT
the deeper, separate problem of persistent AST provenance back to the
authored form (certain to be needed eventually, deliberately deferred;
conflating the two is a category error). Fix: alpha-rename shadowing
binders during desugar (reusing the existing fresh-name machinery that
already mints $mp_N / $lr_N), making the (def, name) key injective
again. No consumer of the uniqueness table changes — they all key by
name, and the name becomes a per-fn injective identity. IR-neutral:
codegen names heap by fresh SSA, never by source binder name. The
pre-desugar hashed form is untouched, so module identity and hash-pin
tests are unaffected.

RED fixtures securing the binder-kind class (the user's gating
condition before proceeding to the uniform-across-kinds fix):
- Let-shadow: the three raw_buf_{int,float,bool}_shadow_rebind tests
  (already in tree, committed f7f4c3b) — assert live == 0.
- Flat-pattern-shadow: a differential test plus its two fixtures
  (flat_pat_shadow_leak.ail shadows the outer binder; _control.ail
  alpha-renames it to a distinct name). The shadow must reach the
  control's live count. Differential by design, to isolate the
  shadow-collapse drop from unrelated baseline drop gaps out of
  #43's scope.

Grounding-check PASS; ail-check parse-gate green on every fenced block.
This commit is contained in:
2026-05-30 12:21:27 +02:00
parent 0f6108e428
commit 0015f3dad1
4 changed files with 361 additions and 0 deletions
+36
View File
@@ -2330,6 +2330,42 @@ fn raw_buf_bool_shadow_rebind_drop_balances_rc_stats() {
);
}
/// A2b completeness (refs #43): the pattern-binder sibling of the
/// Let-shadow leak. A flat-match pattern-binder that shadows an outer
/// same-named heap binder must not leak more than its alpha-renamed
/// equivalent. The uniqueness side-table keys by `(def, binder_name)`
/// (`crates/ailang-check/src/uniqueness.rs`); the shadowing pattern
/// binder `x` collides with the outer `let x`, and the arm-close drop
/// gate (`crates/ailang-codegen/src/match_lower.rs:795`) reads the
/// collapsed (outer) `consume_count` and suppresses the inner
/// payload's drop. `flat_pat_shadow_leak.ail` (inner binder `x`,
/// shadows) and `flat_pat_shadow_control.ail` (inner binder `y`,
/// alpha-renamed) are byte-identical apart from that one name; the
/// shadow must reach the control's live count. RED until binder names
/// are made unique per fn at desugar (the same fix that closes the
/// three `raw_buf_*_shadow_rebind` Let-shadow tests). Differential
/// rather than `live == 0` on purpose: it isolates the shadow-collapse
/// drop from unrelated baseline drop gaps (Box outer cell,
/// implicit-consume of the outer binder) that are out of #43's scope.
#[test]
fn flat_pat_shadow_binder_does_not_leak_more_than_alpha_renamed() {
let (s_out, s_allocs, s_frees, s_live) =
build_and_run_with_rc_stats("flat_pat_shadow_leak.ail");
let (c_out, _c_allocs, _c_frees, c_live) =
build_and_run_with_rc_stats("flat_pat_shadow_control.ail");
assert_eq!(
s_out.trim(),
c_out.trim(),
"shadow and alpha-renamed control must print identically"
);
assert_eq!(
s_live, c_live,
"flat-pattern shadow leaks live={s_live} (allocs={s_allocs} frees={s_frees}) \
vs alpha-renamed control live={c_live}; the shadowing pattern-binder's \
arm-close drop is suppressed by the (def,name) uniqueness-table collision"
);
}
/// raw-buf.4 Float variant: a 2-slot Float RawBuf (1.5 + 2.5),
/// exercising the `double` load/store emits. Prints `4.0` (the `%g`
/// whole-double `.0` fallback).