iter remove-mut-var-assign.1: atomic removal of mut/var/assign
mut/var/assign removed from AILang entirely and atomically. Deleted: Term::Mut/Term::Assign/struct MutVar; the three Form-A keywords + parse_mut/parse_assign + grammar EBNF; the 4 mut CheckError variants; the mut_scope_stack synth threading (param dropped from synth + every internal/external/test caller); the two lower_term arms; and every exhaustive no-_ Term::Mut/Term::Assign match arm across 17 source files — cut in lockstep with DESIGN.md, fixtures, the drift trio, carve-out and roadmap so the schema is honest at every commit. No catch-all wildcard introduced (verified). loop/recur + let/if are the surviving forms. The shared codegen alloca machinery survives (loop reuses it): mut_var_allocas renamed binder_allocas (representation-only, loop codegen byte-identical) and the shared Term::Lam escape guard simplified to !loop_stack.is_empty() with the loop half (LoopBinderCapturedByLambda) byte-equivalent. Feature-acceptance applied inverted: the removed feature fails clause 2 (redundant) and clause 3 (IS the iterated-mutable-state bug class). Behaviour preservation is executable: mut_counter/mut_sum_floats still print 55 after the faithful let/if rewrite. The removal is made executable by the new mut_removed_pin.rs (4 must-fail pins). Independent verification: cargo test --workspace 605/0, zero residual mut symbols in any crate source, loop/recur non-regression all green (55 / 500000500000 / infinite-compiles / the lambda_capturing_loop_binder pin), roundtrip_cli PASS. One DONE_WITH_CONCERNS: a 4th recurrence of the recon-undercount class (in-source mod tests + a drift-pin fn + 5 orphaned mut .ail.json carve-outs + a non-enumerated E0599); all resolved within implementer remit, no behaviour change. Milestone-close audit then fieldtest remain. spec docs/specs/2026-05-18-remove-mut-var-assign.md (grounding PASS) plan docs/plans/remove-mut-var-assign.1.md
This commit is contained in:
@@ -190,17 +190,6 @@ fn walk(t: &Term, out: &mut NonEscapeSet) {
|
||||
walk(source, out);
|
||||
walk(body, out);
|
||||
}
|
||||
// Iter mut.1: mut-vars are alloca-resident scalars (Int /
|
||||
// Float / Bool / Unit) and never participate in heap escape.
|
||||
// Their `init` and `body` and assign `value` terms may still
|
||||
// contain Let-allocation candidates, so walk through.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
walk(&v.init, out);
|
||||
}
|
||||
walk(body, out);
|
||||
}
|
||||
Term::Assign { value, .. } => walk(value, out),
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
walk(&b.init, out);
|
||||
@@ -389,23 +378,6 @@ fn escapes(t: &Term, tainted: &BTreeSet<String>, in_tail: bool) -> bool {
|
||||
}
|
||||
escapes(body, tainted, in_tail)
|
||||
}
|
||||
// Iter mut.1: mut-vars are alloca-resident scalars and do
|
||||
// not themselves contribute to heap escape. Var inits are
|
||||
// evaluated for side-effect (their values are stored into
|
||||
// allocas, not used as the block's value), so they are
|
||||
// walked in non-tail mode. The body IS the block's value,
|
||||
// so it inherits `in_tail`. An assign's value is stored
|
||||
// into an alloca, not used as a tail value — walk
|
||||
// non-tail.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
if escapes(&v.init, tainted, false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
escapes(body, tainted, in_tail)
|
||||
}
|
||||
Term::Assign { value, .. } => escapes(value, tainted, false),
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
if escapes(&b.init, tainted, false) {
|
||||
@@ -526,31 +498,6 @@ fn collect_free_vars(t: &Term, bound: &mut BTreeSet<String>, out: &mut BTreeSet<
|
||||
collect_free_vars(source, bound, out);
|
||||
collect_free_vars(body, bound, out);
|
||||
}
|
||||
// Iter mut.1: a mut-var name binds inside the block. Var
|
||||
// inits before the binding see the outer scope; later inits
|
||||
// and the body see the var as bound. Mirrors `Term::Let`'s
|
||||
// add-then-walk-then-remove pattern, extended over N vars.
|
||||
Term::Mut { vars, body } => {
|
||||
let mut newly: Vec<String> = Vec::new();
|
||||
for v in vars {
|
||||
collect_free_vars(&v.init, bound, out);
|
||||
if bound.insert(v.name.clone()) {
|
||||
newly.push(v.name.clone());
|
||||
}
|
||||
}
|
||||
collect_free_vars(body, bound, out);
|
||||
for n in newly {
|
||||
bound.remove(&n);
|
||||
}
|
||||
}
|
||||
// Iter mut.1: the assigned `name` is a free var unless
|
||||
// bound by an enclosing `Term::Mut`.
|
||||
Term::Assign { name, value } => {
|
||||
if !bound.contains(name) {
|
||||
out.insert(name.clone());
|
||||
}
|
||||
collect_free_vars(value, bound, out);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
let mut newly: Vec<String> = Vec::new();
|
||||
for b in binders {
|
||||
|
||||
Reference in New Issue
Block a user