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:
@@ -906,35 +906,6 @@ fn write_term_prec(out: &mut String, t: &Term, level: usize, parent_prec: u8, ow
|
||||
out.push_str(&body_buf);
|
||||
}
|
||||
}
|
||||
Term::Mut { vars, body } => {
|
||||
// Iter mut.1: prose-side minimal-correctness rendering for
|
||||
// the `(mut ...)` block. The prose-form for mut blocks is
|
||||
// not yet fully designed (the prose surface predates this
|
||||
// milestone); render the block as `mut { var <name>: <ty>
|
||||
// = <init>; ...; <body> }` so a reader can see the shape
|
||||
// without overcommitting to a final prose syntax. Refined
|
||||
// in a follow-on prose iter once the LLM-author signal
|
||||
// arrives.
|
||||
out.push_str("mut {\n");
|
||||
for v in vars {
|
||||
indent(out, level + 1);
|
||||
out.push_str("var ");
|
||||
out.push_str(&v.name);
|
||||
out.push_str(" = ");
|
||||
write_term(out, &v.init, level + 1, owning_module);
|
||||
out.push_str(";\n");
|
||||
}
|
||||
indent(out, level + 1);
|
||||
write_term(out, body, level + 1, owning_module);
|
||||
out.push('\n');
|
||||
indent(out, level);
|
||||
out.push('}');
|
||||
}
|
||||
Term::Assign { name, value } => {
|
||||
out.push_str(name);
|
||||
out.push_str(" := ");
|
||||
write_term(out, value, level, owning_module);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
out.push_str("loop(");
|
||||
for (i, b) in binders.iter().enumerate() {
|
||||
@@ -1133,33 +1104,6 @@ fn count_free_var(name: &str, t: &Term) -> usize {
|
||||
}
|
||||
Term::Clone { value } => count_free_var(name, value),
|
||||
Term::ReuseAs { source, body } => count_free_var(name, source) + count_free_var(name, body),
|
||||
// Iter mut.1: a `Term::Mut` whose `vars` includes a name
|
||||
// matching `name` shadows the outer binding for both later
|
||||
// var inits and the body. Var inits before the shadowing one
|
||||
// still see the outer `name`.
|
||||
Term::Mut { vars, body } => {
|
||||
let mut total = 0usize;
|
||||
let mut shadowed = false;
|
||||
for v in vars {
|
||||
if !shadowed {
|
||||
total += count_free_var(name, &v.init);
|
||||
}
|
||||
if v.name == name {
|
||||
shadowed = true;
|
||||
}
|
||||
}
|
||||
if !shadowed {
|
||||
total += count_free_var(name, body);
|
||||
}
|
||||
total
|
||||
}
|
||||
// Iter mut.1: the assigned `name` field is a use of the
|
||||
// mut-var binding (which may or may not be `name`); the
|
||||
// value is recursed.
|
||||
Term::Assign { name: assign_name, value } => {
|
||||
let n_use = if assign_name == name { 1 } else { 0 };
|
||||
n_use + count_free_var(name, value)
|
||||
}
|
||||
// loop-recur iter 1: a binder named `name` shadows the outer
|
||||
// binding for later binder inits and the body. Inits before
|
||||
// the shadowing binder still see the outer `name`.
|
||||
@@ -1293,50 +1237,9 @@ fn subst_var_with_term(t: &Term, name: &str, replacement: &Term) -> Term {
|
||||
source: Box::new(subst_var_with_term(source, name, replacement)),
|
||||
body: Box::new(subst_var_with_term(body, name, replacement)),
|
||||
},
|
||||
// Iter mut.1: lexical-shadow semantics for mut-vars symmetric
|
||||
// to `count_free_var` above: once a var named `name` is
|
||||
// declared, neither later inits nor the body should be
|
||||
// loop-recur iter 1: lexical-shadow semantics — once a binder
|
||||
// named `name` is declared, later inits and the body are not
|
||||
// rewritten.
|
||||
Term::Mut { vars, body } => {
|
||||
let mut shadowed = false;
|
||||
let new_vars: Vec<ailang_core::ast::MutVar> = vars
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let init = if shadowed {
|
||||
v.init.clone()
|
||||
} else {
|
||||
subst_var_with_term(&v.init, name, replacement)
|
||||
};
|
||||
if v.name == name {
|
||||
shadowed = true;
|
||||
}
|
||||
ailang_core::ast::MutVar {
|
||||
name: v.name.clone(),
|
||||
ty: v.ty.clone(),
|
||||
init,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let body_rw = if shadowed {
|
||||
(**body).clone()
|
||||
} else {
|
||||
subst_var_with_term(body, name, replacement)
|
||||
};
|
||||
Term::Mut {
|
||||
vars: new_vars,
|
||||
body: Box::new(body_rw),
|
||||
}
|
||||
}
|
||||
// Iter mut.1: substitute only inside `value`. The `name`
|
||||
// field is a binding reference (cf. the desugar.rs
|
||||
// `subst_var` arm).
|
||||
Term::Assign { name: assign_name, value } => Term::Assign {
|
||||
name: assign_name.clone(),
|
||||
value: Box::new(subst_var_with_term(value, name, replacement)),
|
||||
},
|
||||
// loop-recur iter 1: lexical-shadow semantics symmetric to
|
||||
// count_free_var — once a binder named `name` is declared,
|
||||
// later inits and the body are not rewritten.
|
||||
Term::Loop { binders, body } => {
|
||||
let mut shadowed = false;
|
||||
let new_binders: Vec<ailang_core::ast::LoopBinder> = binders
|
||||
|
||||
Reference in New Issue
Block a user