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:
2026-05-18 11:06:17 +02:00
parent f355899fdf
commit 07f080256c
48 changed files with 331 additions and 2523 deletions
-69
View File
@@ -1513,37 +1513,6 @@ fn walk_term(
walk_term(source, out, builtins, scope);
walk_term(body, out, builtins, scope);
}
// Iter mut.1: each `var` introduces a new lexical binding;
// its `init` is in the outer scope plus already-declared
// vars; the body sees all of them. Mirrors `Term::Lam`'s
// newly-bound roll-back convention.
Term::Mut { vars, body } => {
let mut newly = Vec::new();
for v in vars {
walk_term(&v.init, out, builtins, scope);
if scope.insert(v.name.clone()) {
newly.push(v.name.clone());
}
}
walk_term(body, out, builtins, scope);
for n in newly {
scope.remove(&n);
}
}
// Iter mut.1: the `value` is walked; the assigned `name` is
// a use of an in-scope mut-var. If the user wrote an
// out-of-scope assign, it surfaces as an unknown identifier
// in the dep walk, which is acceptable for the CLI's
// dependency view (typecheck would already have rejected).
Term::Assign { name, value } => {
if !name.contains('.')
&& !scope.contains(name)
&& !builtins.contains(name.as_str())
{
out.insert(name.clone());
}
walk_term(value, out, builtins, scope);
}
Term::Loop { binders, body } => {
let mut newly = Vec::new();
for b in binders {
@@ -2765,44 +2734,6 @@ fn rewrite_def(
changed,
);
}
// Iter mut.1: rewrite types embedded in each `MutVar.ty`
// (via `rewrite_type` — mut-vars carry full Type
// annotations) and recurse into each var's `init` and
// the block's body. `Term::Assign` has no embedded type.
Term::Mut { vars, body } => {
for v in vars {
rewrite_type(
&mut v.ty,
owning_module,
local_types,
import_names,
changed,
);
rewrite_term(
&mut v.init,
owning_module,
local_types,
import_names,
changed,
);
}
rewrite_term(
body,
owning_module,
local_types,
import_names,
changed,
);
}
Term::Assign { value, .. } => {
rewrite_term(
value,
owning_module,
local_types,
import_names,
changed,
);
}
Term::Loop { binders, body } => {
for b in binders {
rewrite_type(