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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user