Files
AILang/examples/test_mut_assign_outside_mut.ail.json
T
Brummel b24718a5ff iter mut.2: typecheck for Term::Mut + Term::Assign
Replaces the iter mut.1 dispatch stubs in synth with real typecheck
logic. Codegen stubs stay (deferred to mut.3); examples/mut.ail now
typechecks clean but still cannot run end-to-end.

Concretely:

- crates/ailang-check/src/lib.rs: three new CheckError variants
  (MutAssignOutOfScope, AssignTypeMismatch, UnsupportedMutVarType)
  with #[error('[code]: msg')] Display attrs matching the cli-diag-
  human bracketed-code convention; code() + ctx() arms emit
  kebab-case codes and structured JSON payloads.

- synth signature: new parameter mut_scope_stack: &mut Vec<IndexMap<
  String, Type>> after locals, threaded through every recursive call
  site (synth's 18 internal calls + mono.rs:712/1337 re-synth +
  lift.rs:723 + builtins.rs test helpers + the mq.3 in-test helper
  at lib.rs:6663). Stack is per-walk and lexically scoped — push on
  Term::Mut entry, pop on exit. Each frame is an IndexMap so
  declaration order is preserved for the diagnostic 'available' list.

- Term::Var resolution: prepended with innermost-first mut-scope
  lookup. Mut-vars are monomorphic — no Forall instantiation, no
  free_fn_owner recording. Innermost-wins shadowing falls out of
  the iteration order.

- Term::Mut arm: gate var types to Int/Float/Bool/Unit only via
  a small is_supported_mut_var_type helper; each var's init is
  synth'd in the in-progress scope (the var itself is not yet
  bound, so it does not self-shadow during init); push completed
  frame, synth body, pop. Block's type is body's type.

- Term::Assign arm: walk mut_scope_stack innermost-first looking for
  name. On miss: MutAssignOutOfScope with available flattened
  across all frames (innermost-first, deduplicated, preserving
  order). On hit: synth value, unify against declared type — on
  mismatch emit AssignTypeMismatch with rendered types; on success
  produce Type::unit().

- Five .ail.json fixtures under examples/ exercising each
  diagnostic plus a positive nested-shadow sanity case, driven by
  the new pin test crates/ailang-check/tests/mut_typecheck_pin.rs
  (load_workspace + check_workspace + assert exact codes).

- carve_out_inventory.rs EXPECTED extended 7 → 12 to cover the new
  negative-fixture set (consistent with the form-a-default-authoring
  spec §C4(b) precedent for type-rejection fixtures staying as
  .ail.json-only).

Plan deviations from recon: three additional synth() call sites
beyond the four enumerated (lift.rs:723, builtins.rs x2,
lib.rs:6663) surfaced via the build-red structural signal; each
threaded with a fresh empty stack. carve_out_inventory.rs extension
was required by the existing pin but not named in the recon — a
documentary concern for the next planner pass.

Tests: 579 → 592 green; examples/mut.ail typechecks clean
('ok (26 symbols across 2 modules)') via cargo run --bin ail --
check examples/mut.ail; cargo build green; full workspace test
green.

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

Refs: docs/specs/2026-05-15-mut-local.md, docs/plans/2026-05-15-iter-mut.2.md.
2026-05-15 01:37:25 +02:00

25 lines
559 B
JSON

{
"schema": "ailang/v0",
"name": "test_mut_assign_outside_mut",
"imports": [],
"defs": [
{
"kind": "fn",
"name": "main",
"type": {
"k": "fn",
"params": [],
"ret": { "k": "con", "name": "Unit" },
"effects": []
},
"params": [],
"doc": "Iter mut.2: Term::Assign with no enclosing Term::Mut ancestor fires mut-assign-out-of-scope.",
"body": {
"t": "assign",
"name": "x",
"value": { "t": "lit", "lit": { "kind": "int", "value": 1 } }
}
}
]
}