Files
AILang/docs/journals/2026-05-18-iter-remove-mut-var-assign.1.md
T
Brummel 07f080256c 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
2026-05-18 11:06:17 +02:00

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 real ailang_surface::parse + ailang_core::ast::Term serde 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/MutVar from ast.rs; every exhaustive no-_ Term::Mut/Term::Assign match arm across 17 source files; parse.rs dispatch + parse_mut/parse_assign + reservation + grammar EBNF (incl. the line-43 | mut-term | assign-term alternation that referenced the deleted productions); print.rs arms; the 4 CheckError variants + code()/ctx(); the synth arms + Term::Var mut-scope lookup; the mut_scope_stack param removed from synth + every internal/external/test caller; escape guard simplified to loop_stack-only; lower_term Mut/Assign arms deleted; mut_var_allocasbinder_allocas renamed at all surviving sites + lambda.rs save/restore. Dead is_supported_mut_var_type deleted. cargo build --workspace 0 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 in ct1_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::Assign JSONC 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 (only resolve names + assign hashes survives — 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..4 to the plan's exact faithful let/if shapes; present-tense docs, no nostalgia. Verified: mut_counter/mut_sum_floats→55, factorial→120, horner→18, classify 22→2, has_small_factor 91→true; zero mut/var/assign construct tokens remain.
  • remove-mut-var-assign.1.6: carve_out_inventory.rs 18→12 (EXPECTED + header doc) AND deleted the 5 orphaned mut .ail.json carve-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_cli PASS.

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 deleted Term::Mut/MutVar/deleted-CheckError types — arg-trimming alone cannot make them compile. The faithful execution (identical rationale to Task-3's mut_typecheck_pin.rs whole-file deletion) was to delete the pure-mut-behaviour test fns wholesale: ~10 in-source #[cfg(test)] mod tests fns in ailang-check/src/lib.rs (mut_var_resolution_, mut_block_, assign_, mut_typecheck_diagnostics_have_kebab_codes, mut_var_captured_by_lambda_, lambda_inside_mut_*), the design_md_documents_the_accumulator_over_iteration_idiom test fn (pins deleted DESIGN.md prose), the 5 orphaned mut .ail.json carve-out files (Task 5 only deleted 1 of 6; the inventory test asserts disk==EXPECTED so all 6 must go), and the Term::Mut/Term::Assign arms in two non-enumerated test helpers (ail/tests/codegen_import_map_fallback_pin.rs — a hard E0599 the recon missed entirely; ail/tests/e2e.rs doc-comments). ~30 now-dangling doc-comments/comments that cross-referenced deleted symbols (mirrors Term::Mut, MutVarCapturedByLambda, the loop_recur_typecheck_pin.rs RED-state doc) were reworded so the source has zero trace/post-mortem (spec acceptance). The kept e2e bodies mut_counter_prints_55/mut_sum_floats_prints_55 were 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:1620 tmp_dir is a pre-existing never used test-helper warning, NOT introduced by this iter (it surfaces only in the test-target build; cargo build --workspace is 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