iter mut.1: AST extension + Form A surface for local mutable state

First iteration of the mut-local milestone (foundation step on the
Stateful-islands roadmap path). Lands the schema + surface tier:
Term::Mut, Term::Assign, and the nested MutVar struct become
first-class AST nodes that round-trip cleanly through Form A.
Typecheck and codegen recognition are deferred to mut.2 and mut.3
per the spec's out-of-iteration boundary; reaching either dispatch
entry point with these variants produces CheckError::Internal /
CodegenError::Internal with a 'deferred to iter mut.{2,3}' message.

Concretely:

- crates/ailang-core/src/ast.rs: two new Term variants behind
  #[serde(tag = 't')]; pub struct MutVar { name, ty, init } adjacent
  to Arm. Two canonical-bytes pin tests for the explicit-empty-vars
  serialisation and the assign round-trip.

- ~25 substantive Term-walker arms across ailang-core/desugar,
  ailang-core/workspace, ailang-check (lib + lift + linearity + mono
  + pre_desugar_validation + reuse_shape + uniqueness),
  ailang-codegen (escape + lambda + lib), ailang-prose, and
  crates/ail/src/main.rs. Universal policy: substantive recurse-into-
  children at every site; only the two dispatch entry points
  (synth in ailang-check, lower_term in ailang-codegen) stub with
  Internal-error. One test-side walker arm in
  crates/ail/tests/codegen_import_map_fallback_pin.rs not
  enumerated by the plan was added as well (defensive recursion).

- ailang-surface: parse_mut + parse_assign helpers; Term::Mut
  body desugared from a flat statement sequence into a right-folded
  Term::Seq chain inside the JSON-AST. Print arms in print.rs match
  the parser convention. EBNF prologue + crates/ailang-core/specs/
  form_a.md productions updated. Four new parser pin tests cover
  the empty-mut, single-var, body-required, and vars-only-no-body
  cases.

- Drift + coverage tests extended: design_schema_drift.rs adds two
  exemplars + match arms; schema_coverage.rs adds two VariantTag
  entries + EXPECTED_VARIANTS + visit_term arms; spec_drift.rs adds
  two exemplars + match arms. DESIGN.md §'Term (expression)' gets
  jsonc-blocked schemas for the two new variants.

- examples/mut.ail: six-fn round-trip fixture exercising empty mut,
  single-var, two-var, nested-shadow, and the four supported scalar
  return types (Int, Float, Bool, Unit). The round_trip auto-glob
  and schema_coverage corpus walker both pick it up.

Plan deviation: the plan named lib.rs:2572 as the typecheck
dispatch stub site, but that line is actually verify_tail_positions
(substantive walker). The real dispatch is synth (3403-area, stub
at 3489); the orchestrator routed correctly.

Tests: 564 → 579 green; cargo build green; round-trip green for
the new fixture; all drift + coverage tests green.

Journal: docs/journals/2026-05-15-iter-mut.1.md.

Refs: docs/specs/2026-05-15-mut-local.md, docs/plans/2026-05-15-iter-mut.1.md.
This commit is contained in:
2026-05-15 01:10:56 +02:00
parent 60e4559e31
commit 7b92719244
27 changed files with 1404 additions and 2 deletions
@@ -0,0 +1,27 @@
{
"iter_id": "mut.1",
"date": "2026-05-15",
"mode": "standard",
"outcome": "DONE",
"tasks_total": 6,
"tasks_completed": 6,
"reloops_per_task": {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0
},
"review_loops_spec": 0,
"review_loops_quality": 0,
"blocked_reason": null,
"notes_per_task": {
"1": "MutVar derives diverged from plan literal (`PartialEq + Eq` omitted; followed Arm's pattern per plan's own stated justification); recorded as concern; no functional impact",
"2": "plan-recon misindexed the typecheck dispatch entry — listed lib.rs:2572 (actually `verify_tail_positions`, substantive territory) instead of the real entry `synth` at lib.rs:3403; stub placed at the actual entry; line 2572 received a substantive arm; one test-side walker site (`crates/ail/tests/codegen_import_map_fallback_pin.rs:62`) unenumerated by plan, fixed inline (build-green required); print arms in `print.rs` landed during Task 2 build-unblock rather than waiting for Task 4 (benign sequencing shift)",
"3": "dispatch stubs use `CheckError::Internal(String)` and `CodegenError::Internal(String)` (single-field tuple variant); verified against existing variant declarations",
"4": "plan pseudo-code used helper names that don't exist (`peek_is_open_paren_with_head`, `expect_head`, `ParseError::at`); substituted actual API (`expect_lparen`, `expect_keyword`, `expect_ident`, `parse_type`, `parse_term`, `expect_rparen`, `peek_head_ident`); 4 RED-first parser tests added, all green post-parser",
"5": "all three drift tests green after extensions; `schema_coverage` deliberately RED before Task 6 per plan Step 6",
"6": "fixture exercises all four supported scalar types (Int, Float, Bool, Unit) plus empty / single-var / two-var / nested-shadow cases; full workspace 579/579 green"
}
}