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:
@@ -342,7 +342,6 @@ mod tests {
|
||||
let mut env = Env::default();
|
||||
install(&mut env);
|
||||
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
||||
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
||||
let mut effects: BTreeSet<String> = BTreeSet::new();
|
||||
let mut subst = Subst::default();
|
||||
let mut counter: u32 = 0;
|
||||
@@ -350,13 +349,12 @@ mod tests {
|
||||
let mut free_fn_calls = Vec::new();
|
||||
let mut warnings: Vec<crate::diagnostic::Diagnostic> = Vec::new();
|
||||
// loop-recur iter 2: test helper synths one term from top-of-
|
||||
// body — fresh empty loop-stack (mirrors mut_scope_stack).
|
||||
// body — fresh empty loop-stack.
|
||||
let mut loop_stack: Vec<Vec<(String, Type)>> = Vec::new();
|
||||
let ty = crate::synth(
|
||||
t,
|
||||
&env,
|
||||
&mut locals,
|
||||
&mut mut_scope_stack,
|
||||
&mut loop_stack,
|
||||
&mut effects,
|
||||
"<test>",
|
||||
@@ -589,7 +587,6 @@ mod tests {
|
||||
let mut env = Env::default();
|
||||
install(&mut env);
|
||||
let mut locals: IndexMap<String, Type> = IndexMap::new();
|
||||
let mut mut_scope_stack: Vec<IndexMap<String, Type>> = Vec::new();
|
||||
let mut effects: BTreeSet<String> = BTreeSet::new();
|
||||
let mut subst = Subst::default();
|
||||
let mut counter: u32 = 0;
|
||||
@@ -597,13 +594,12 @@ mod tests {
|
||||
let mut free_fn_calls = Vec::new();
|
||||
let mut warnings: Vec<crate::diagnostic::Diagnostic> = Vec::new();
|
||||
// loop-recur iter 2: test helper synths one term from top-of-
|
||||
// body — fresh empty loop-stack (mirrors mut_scope_stack).
|
||||
// body — fresh empty loop-stack.
|
||||
let mut loop_stack: Vec<Vec<(String, Type)>> = Vec::new();
|
||||
let err = crate::synth(
|
||||
&term,
|
||||
&env,
|
||||
&mut locals,
|
||||
&mut mut_scope_stack,
|
||||
&mut loop_stack,
|
||||
&mut effects,
|
||||
"<test>",
|
||||
|
||||
+62
-689
File diff suppressed because it is too large
Load Diff
@@ -375,28 +375,6 @@ impl<'a> Lifter<'a> {
|
||||
source: Box::new(self.lift_in_term(source, locals, in_def)?),
|
||||
body: Box::new(self.lift_in_term(body, locals, in_def)?),
|
||||
}),
|
||||
// Iter mut.1: lift letrecs out of each var's init and the
|
||||
// body. Mut-vars cannot themselves participate in a
|
||||
// letrec capture set (letrec bodies are fn-typed and
|
||||
// mut-vars are scalar values), so the var bindings do
|
||||
// not extend `locals` for the lift walk.
|
||||
Term::Mut { vars, body } => Ok(Term::Mut {
|
||||
vars: vars
|
||||
.iter()
|
||||
.map(|v| {
|
||||
Ok(ailang_core::ast::MutVar {
|
||||
name: v.name.clone(),
|
||||
ty: v.ty.clone(),
|
||||
init: self.lift_in_term(&v.init, locals, in_def)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
body: Box::new(self.lift_in_term(body, locals, in_def)?),
|
||||
}),
|
||||
Term::Assign { name, value } => Ok(Term::Assign {
|
||||
name: name.clone(),
|
||||
value: Box::new(self.lift_in_term(value, locals, in_def)?),
|
||||
}),
|
||||
Term::Loop { binders, body } => Ok(Term::Loop {
|
||||
binders: binders
|
||||
.iter()
|
||||
@@ -739,15 +717,11 @@ impl<'a> Lifter<'a> {
|
||||
// (e.g. class-method-shadowed-by-fn) have already been
|
||||
// surfaced upstream by `check_workspace`. Discard here.
|
||||
let mut warnings_discarded: Vec<crate::diagnostic::Diagnostic> = Vec::new();
|
||||
// Iter mut.2: lift's letrec-capture synth re-entry walks one
|
||||
// term at a time, beginning from a top-of-body position; fresh
|
||||
// empty mut-scope stack is correct.
|
||||
let mut mut_scope_stack: Vec<indexmap::IndexMap<String, crate::Type>> = Vec::new();
|
||||
// loop-recur iter 2: lift's letrec-capture synth re-entry walks
|
||||
// one term at a time from a top-of-body position; fresh empty
|
||||
// loop-stack is correct (any `Term::Loop` pushes its own frame).
|
||||
let mut loop_stack: Vec<Vec<(String, crate::Type)>> = Vec::new();
|
||||
let ty = synth(t, &self.env, locals, &mut mut_scope_stack, &mut loop_stack, &mut effects, in_def, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings_discarded)?;
|
||||
let ty = synth(t, &self.env, locals, &mut loop_stack, &mut effects, in_def, &mut subst, &mut counter, &mut residuals, &mut free_fn_calls, &mut warnings_discarded)?;
|
||||
Ok(subst.apply(&ty))
|
||||
}
|
||||
}
|
||||
@@ -778,12 +752,6 @@ fn contains_any_letrec(m: &Module) -> bool {
|
||||
Term::Clone { value } => term_has_letrec(value),
|
||||
// Iter 18d.1: structural recursion through both children.
|
||||
Term::ReuseAs { source, body } => term_has_letrec(source) || term_has_letrec(body),
|
||||
// Iter mut.1: any var init or the body can contain a
|
||||
// letrec; the assign's value can too.
|
||||
Term::Mut { vars, body } => {
|
||||
vars.iter().any(|v| term_has_letrec(&v.init)) || term_has_letrec(body)
|
||||
}
|
||||
Term::Assign { value, .. } => term_has_letrec(value),
|
||||
Term::Loop { binders, body } => {
|
||||
binders.iter().any(|b| term_has_letrec(&b.init)) || term_has_letrec(body)
|
||||
}
|
||||
|
||||
@@ -592,23 +592,6 @@ impl<'a> Checker<'a> {
|
||||
// standard position rules.
|
||||
self.walk(body, pos);
|
||||
}
|
||||
// Iter mut.1: mut-vars are alloca-resident scalars (Int /
|
||||
// Float / Bool / Unit per spec §"Iteration mut.1") — they
|
||||
// are not RC-managed and do not participate in the
|
||||
// linearity analysis. Walk children defensively so any
|
||||
// RC-managed value referenced inside an init or the body
|
||||
// is still tracked.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
self.walk(&v.init, Position::Consume);
|
||||
}
|
||||
self.walk(body, pos);
|
||||
}
|
||||
Term::Assign { value, .. } => {
|
||||
// The assigned `name` is a mut-var (alloca slot, not
|
||||
// a tracked binder). Walk the `value` normally.
|
||||
self.walk(value, Position::Consume);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
self.walk(&b.init, Position::Consume);
|
||||
@@ -804,8 +787,6 @@ fn make_reuse_as_source_not_bare_var(def: &str, source: &Term, body: &Term) -> D
|
||||
Term::Seq { .. } => "seq",
|
||||
Term::Clone { .. } => "clone",
|
||||
Term::ReuseAs { .. } => "reuse-as",
|
||||
Term::Mut { .. } => "mut",
|
||||
Term::Assign { .. } => "assign",
|
||||
Term::Loop { .. } => "loop",
|
||||
Term::Recur { .. } => "recur",
|
||||
};
|
||||
@@ -933,20 +914,6 @@ fn any_sub_binder_consumed_for(
|
||||
any_sub_binder_consumed_for(source, pname, uniq, def_name, ctors)
|
||||
|| any_sub_binder_consumed_for(body, pname, uniq, def_name, ctors)
|
||||
}
|
||||
// Iter mut.1: mut-vars are scalar (Int/Float/Bool/Unit) and
|
||||
// do not participate in the consume-tracking heuristic; the
|
||||
// surrounding analysis only cares about heap-typed param
|
||||
// consumption. Recurse into children defensively so any
|
||||
// genuine consume-of-`pname` inside a var init / body /
|
||||
// assign value still surfaces.
|
||||
Term::Mut { vars, body } => {
|
||||
vars.iter().any(|v| {
|
||||
any_sub_binder_consumed_for(&v.init, pname, uniq, def_name, ctors)
|
||||
}) || any_sub_binder_consumed_for(body, pname, uniq, def_name, ctors)
|
||||
}
|
||||
Term::Assign { value, .. } => {
|
||||
any_sub_binder_consumed_for(value, pname, uniq, def_name, ctors)
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
binders.iter().any(|b| {
|
||||
any_sub_binder_consumed_for(&b.init, pname, uniq, def_name, ctors)
|
||||
|
||||
@@ -713,19 +713,14 @@ pub fn collect_mono_targets(
|
||||
// discards warnings — typecheck has already run and surfaced any
|
||||
// shadow warnings; mono is a post-typecheck pass.
|
||||
let mut warnings_discarded: Vec<crate::diagnostic::Diagnostic> = Vec::new();
|
||||
// Iter mut.2: mono re-synth begins from top-of-body — empty
|
||||
// mut-scope, since any `Term::Mut` will push its own frame as the
|
||||
// walk descends.
|
||||
let mut mut_scope_stack: Vec<indexmap::IndexMap<String, crate::Type>> = Vec::new();
|
||||
// loop-recur iter 2: mono re-synth begins from top-of-body —
|
||||
// empty loop-stack (any `Term::Loop` pushes its own frame as the
|
||||
// walk descends), mirroring the fresh mut-scope above.
|
||||
// walk descends).
|
||||
let mut loop_stack: Vec<Vec<(String, crate::Type)>> = Vec::new();
|
||||
crate::synth(
|
||||
&f.body,
|
||||
&env,
|
||||
&mut locals,
|
||||
&mut mut_scope_stack,
|
||||
&mut loop_stack,
|
||||
&mut effects,
|
||||
&f.name,
|
||||
@@ -1235,19 +1230,6 @@ fn rewrite_mono_calls(
|
||||
rewrite_mono_calls(source, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
rewrite_mono_calls(body, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
}
|
||||
// Iter mut.1: visitor-shape recurse with mutable access.
|
||||
// Mut-vars do not declare class-method or poly-fn names; the
|
||||
// var inits, the body, and assign values can though, so walk
|
||||
// them in place.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars.iter_mut() {
|
||||
rewrite_mono_calls(&mut v.init, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
}
|
||||
rewrite_mono_calls(body, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
}
|
||||
Term::Assign { value, .. } => {
|
||||
rewrite_mono_calls(value, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders.iter_mut() {
|
||||
rewrite_mono_calls(&mut b.init, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, caller_module, ordered_targets, cursor, locals);
|
||||
@@ -1359,19 +1341,14 @@ pub(crate) fn collect_residuals_ordered(
|
||||
// discards warnings — typecheck has already run and surfaced any
|
||||
// shadow warnings; mono is a post-typecheck pass.
|
||||
let mut warnings_discarded: Vec<crate::diagnostic::Diagnostic> = Vec::new();
|
||||
// Iter mut.2: mono re-synth begins from top-of-body — empty
|
||||
// mut-scope, since any `Term::Mut` will push its own frame as the
|
||||
// walk descends.
|
||||
let mut mut_scope_stack: Vec<indexmap::IndexMap<String, crate::Type>> = Vec::new();
|
||||
// loop-recur iter 2: mono re-synth begins from top-of-body —
|
||||
// empty loop-stack (any `Term::Loop` pushes its own frame as the
|
||||
// walk descends), mirroring the fresh mut-scope above.
|
||||
// walk descends).
|
||||
let mut loop_stack: Vec<Vec<(String, crate::Type)>> = Vec::new();
|
||||
crate::synth(
|
||||
&f.body,
|
||||
&env,
|
||||
&mut locals,
|
||||
&mut mut_scope_stack,
|
||||
&mut loop_stack,
|
||||
&mut effects,
|
||||
&f.name,
|
||||
@@ -1625,19 +1602,6 @@ fn interleave_slots(
|
||||
interleave_slots(source, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
interleave_slots(body, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
}
|
||||
// Iter mut.1: visitor-shape recurse, symmetric to
|
||||
// `rewrite_mono_calls` above. Mut-vars themselves do not
|
||||
// contribute slots; their init and body terms (and assign
|
||||
// values) can.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
interleave_slots(&v.init, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
}
|
||||
interleave_slots(body, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
}
|
||||
Term::Assign { value, .. } => {
|
||||
interleave_slots(value, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
interleave_slots(&b.init, method_to_candidate_classes, poly_free_fns, poly_free_fn_ccounts, class_slots, free_fn_slots, class_cur, free_cur, locals, out);
|
||||
|
||||
@@ -120,19 +120,6 @@ fn walk_term(t: &Term) -> Result<(), CheckError> {
|
||||
walk_term(source)?;
|
||||
walk_term(body)
|
||||
}
|
||||
// Iter mut.1: pre-desugar validation has nothing specific to
|
||||
// assert about mut blocks (the only invariant this pass
|
||||
// enforces today is "no Float in literal patterns" — mut
|
||||
// var inits are Terms, but they can hold any literal, and
|
||||
// patterns inside a body are visited via the normal
|
||||
// `Term::Match` arm). Recurse into children.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
walk_term(&v.init)?;
|
||||
}
|
||||
walk_term(body)
|
||||
}
|
||||
Term::Assign { value, .. } => walk_term(value),
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
walk_term(&b.init)?;
|
||||
|
||||
@@ -253,18 +253,6 @@ impl<'a> Checker<'a> {
|
||||
self.walk(body);
|
||||
self.check_reuse_as(source, body);
|
||||
}
|
||||
// Iter mut.1: mut-vars are scalar (Int/Float/Bool/Unit)
|
||||
// and cannot host an allocating ctor body, so the
|
||||
// reuse-as path-ctor analysis has nothing to refine here.
|
||||
// Recurse defensively into children so any reuse-as
|
||||
// nested inside a var init or the body is still checked.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
self.walk(&v.init);
|
||||
}
|
||||
self.walk(body);
|
||||
}
|
||||
Term::Assign { value, .. } => self.walk(value),
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
self.walk(&b.init);
|
||||
|
||||
@@ -336,22 +336,6 @@ impl<'a> Walker<'a> {
|
||||
self.walk(source, Position::Consume);
|
||||
self.walk(body, pos);
|
||||
}
|
||||
// Iter mut.1: mut-vars are alloca-resident scalars and
|
||||
// are NOT RC-tracked binders. They do not contribute to
|
||||
// uniqueness counts (the alloca slot is the per-fn
|
||||
// storage, not a heap allocation). Walk children
|
||||
// defensively so any RC-managed value passed through a
|
||||
// var init / body / assign-value still updates the
|
||||
// appropriate uniqueness counts.
|
||||
Term::Mut { vars, body } => {
|
||||
for v in vars {
|
||||
self.walk(&v.init, Position::Consume);
|
||||
}
|
||||
self.walk(body, pos);
|
||||
}
|
||||
Term::Assign { value, .. } => {
|
||||
self.walk(value, Position::Consume);
|
||||
}
|
||||
Term::Loop { binders, body } => {
|
||||
for b in binders {
|
||||
self.walk(&b.init, Position::Consume);
|
||||
|
||||
Reference in New Issue
Block a user