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
7.7 KiB
iter remove-mut-var-assign.1 — atomic removal of mut/var/assign
Date: 2026-05-18
Started from: f355899fdf
Status: DONE
Tasks completed: 6 of 6
Summary
The local-mutable-state construct (Term::Mut / Term::Assign /
struct MutVar, the mut/var/assign Form-A keywords, the 4
mut CheckError variants, the mut_scope_stack synth threading,
the two lower_term arms) is removed from AILang entirely and
atomically — AST + surface + checker + codegen + DESIGN.md +
fixtures + drift trio + carve-out + roadmap cut together so the
schema is honest at every commit. loop/recur + let/if are
the surviving forms. The shared codegen alloca machinery survives
(loop needs it); its now-misnamed mut_var_allocas field is renamed
binder_allocas and the shared Term::Lam escape guard is
simplified to !loop_stack.is_empty() with the loop half kept
byte-equivalent. Removal is symmetric to the additive mut.1
introduction: serde is tag-based so no surviving module's
canonical-JSON hash moves; loop/recur codegen is byte-identical
(the rename is representation-only). Full cargo test --workspace
605/0; loop/recur non-regression hard gates all green
(55 / 500000500000 / infinite-compiles / the
lambda_capturing_loop_binder pin). The behaviour-preservation
proof is executable: mut_counter/mut_sum_floats still print 55
after the faithful let/if rewrite. The "removal made executable"
gate is the new mut_removed_pin.rs (4 must-fail pins: mut/
assign keyword rejected, {"t":"mut"}/{"t":"assign"} serde
unknown-variant).
Per-task notes
- remove-mut-var-assign.1.1: created
crates/ailang-surface/tests/mut_removed_pin.rs— 4 RED→GREEN must-fail pins; harness compiled against the realailang_surface::parse+ailang_core::ast::Termserde public API unchanged (no adaptation needed). RED-verified (4 fail with the exact expected messages), GREEN after T2. - remove-mut-var-assign.1.2: the atomic Rust cut. Deleted
Term::Mut/Term::Assign/MutVarfrom ast.rs; every exhaustive no-_Term::Mut/Term::Assignmatch arm across 17 source files; parse.rs dispatch +parse_mut/parse_assign+ reservation + grammar EBNF (incl. the line-43| mut-term | assign-termalternation that referenced the deleted productions); print.rs arms; the 4CheckErrorvariants +code()/ctx(); the synth arms +Term::Varmut-scope lookup; themut_scope_stackparam removed fromsynth+ every internal/external/test caller; escape guard simplified toloop_stack-only;lower_termMut/Assign arms deleted;mut_var_allocas→binder_allocasrenamed at all surviving sites + lambda.rs save/restore. Deadis_supported_mut_var_typedeleted.cargo build --workspace0 errors + 4 Task-1 pins GREEN (the only T2 gate). NO_ =>wildcard introduced. - remove-mut-var-assign.1.3: deleted
mut_typecheck_pin.rs(whole file); dropped the 4 mut rows inct1_check_cli.rs(kept the surviving loop-binder row, renamed the test + reworded doc honestly); dropped the mut/assign entries + match arms in the drift trio (schema_coverage,design_schema_drift,spec_drift). - remove-mut-var-assign.1.4: DESIGN.md hard-delete — the
Term::Mut/Term::AssignJSONC blocks, the mut post-schema paragraph (loop-binder half kept + reworded standalone, present-tense, points at the live loop-recur spec), the "Local mutable state" feature bullet, the accumulator-idiom paragraph. Clause-3 rationale ("iterated-mutable-state reasoning", 99/107) untouched. Zero construct residue (onlyresolve names + assign hashessurvives — a hashing-pipeline verb, plan-allowed). - remove-mut-var-assign.1.5: deleted the 3 rejection-probe fixtures; rewrote
mut.ail(6 fns),mut_counter.ail/mut_sum_floats.ail,mut-local_1..4to the plan's exact faithfullet/ifshapes; present-tense docs, no nostalgia. Verified:mut_counter/mut_sum_floats→55, factorial→120, horner→18,classify 22→2,has_small_factor 91→true; zeromut/var/assignconstruct tokens remain. - remove-mut-var-assign.1.6:
carve_out_inventory.rs18→12 (EXPECTED + header doc) AND deleted the 5 orphaned mut.ail.jsoncarve-out files the test's disk-inventory assertion requires gone; roadmap false-foundation**Progress.**mut-local sub-bullet deleted (milestone header kept). FINAL full-suite green gate 605/0; loop/recur non-regression all green;roundtrip_cliPASS.
Concerns
- DONE_WITH_CONCERNS (T2/T3/T6, recon-undercount class,
feedback_plan_pseudo_vs_reality/feedback_recon_undercount): the plan's Step-7 line-list framed several sites as "delete the argument at the test-only synth callers", but those test fns' bodies construct deletedTerm::Mut/MutVar/deleted-CheckErrortypes — arg-trimming alone cannot make them compile. The faithful execution (identical rationale to Task-3'smut_typecheck_pin.rswhole-file deletion) was to delete the pure-mut-behaviour test fns wholesale: ~10 in-source#[cfg(test)] mod testsfns inailang-check/src/lib.rs(mut_var_resolution_, mut_block_, assign_, mut_typecheck_diagnostics_have_kebab_codes, mut_var_captured_by_lambda_, lambda_inside_mut_*), thedesign_md_documents_the_accumulator_over_iteration_idiomtest fn (pins deleted DESIGN.md prose), the 5 orphaned mut.ail.jsoncarve-out files (Task 5 only deleted 1 of 6; the inventory test asserts disk==EXPECTED so all 6 must go), and theTerm::Mut/Term::Assignarms in two non-enumerated test helpers (ail/tests/codegen_import_map_fallback_pin.rs— a hard E0599 the recon missed entirely;ail/tests/e2e.rsdoc-comments). ~30 now-dangling doc-comments/comments that cross-referenced deleted symbols (mirrors Term::Mut,MutVarCapturedByLambda, theloop_recur_typecheck_pin.rsRED-state doc) were reworded so the source has zero trace/post-mortem (spec acceptance). The kept e2e bodiesmut_counter_prints_55/mut_sum_floats_prints_55were preserved byte-identical per plan line-57's explicit "must stay green" directive — only their lying doc-comments were corrected. None of these are gratuitous; every one is strictly entailed by the atomic removal + the plan-stated final green gate. The class recurred enough (in-source tests, drift-pin test fn, orphaned carve-out files, non-enumerated test helpers) to be a planner-recon defect worth a follow-up countermeasure, not a per-iter fix.
Known debt
crates/ailang-core/src/workspace.rs:1620tmp_diris a pre-existingnever usedtest-helper warning, NOT introduced by this iter (it surfaces only in the test-target build;cargo build --workspaceis 0 warnings). Out of scope (no surrounding cleanup); left untouched.
Files touched
Source (17): ail/src/main.rs, ailang-check/src/{builtins,lib,lift,linearity,mono,pre_desugar_validation,reuse_shape,uniqueness}.rs, ailang-codegen/src/{escape,lambda,lib}.rs, ailang-core/src/{ast,desugar,workspace}.rs, ailang-prose/src/lib.rs, ailang-surface/src/{parse,print}.rs Tests (modified): ailang-core/tests/{carve_out_inventory,design_schema_drift,schema_coverage,spec_drift}.rs, ail/tests/{ct1_check_cli,codegen_import_map_fallback_pin,e2e}.rs, ailang-check/tests/loop_recur_typecheck_pin.rs Tests (created): ailang-surface/tests/mut_removed_pin.rs Tests (deleted): ailang-check/tests/mut_typecheck_pin.rs Docs: docs/DESIGN.md, docs/roadmap.md Fixtures (rewritten): examples/mut.ail, examples/mut_counter.ail, examples/mut_sum_floats.ail, examples/fieldtest/mut-local_{1,2,3,4}.ail Fixtures (deleted, 8): examples/fieldtest/mut-local_{5,6}.ail, examples/test_mut_{assign_out_of_scope,assign_outside_mut,assign_type_mismatch,nested_shadow_legal,var_captured_by_lambda,var_unsupported_type}.ail.json
Stats
bench/orchestrator-stats/2026-05-18-iter-remove-mut-var-assign.1.json