iter mut.4-tidy + audit close — mut-local milestone closed
Tidy iter addressing the audit-mut-local drift. Plus paired baseline
update on bench/baseline_compile.json (audit-skill discipline).
Architect [high] items closed:
1. CheckError::MutVarCapturedByLambda rejects lambdas whose body
free vars include a mut-var of the enclosing mut_scope_stack.
Uses the existing ailang_core::desugar::free_vars_in_term walker
(which honours Term::Match pattern bindings). The scan runs only
when mut_scope_stack is non-empty.
2. crates/ailang-codegen/src/lambda.rs: the capture-not-in-locals
path that previously raised CodegenError::Internal blaming the
typechecker now uses unreachable!. The companion comment block
was rewritten to state the current reality.
Architect [medium] items closed (stale mut.1-stub history comments):
3. docs/DESIGN.md §'Term (expression)' mut/assign block: the
trailing paragraph describing the iter mut.1 stub state was
replaced with one describing the current mut.3-end-state. The
inline jsonc comment on {'t': 'assign'} was updated to drop the
'deferred to mut.2/mut.3' language.
4. crates/ailang-codegen/src/lib.rs: the stale comment block above
the real Term::Mut arm describing the mut.1 stub was removed
entirely.
Other:
- Short-circuit on empty mut_scope_stack in synth's Term::Var arm:
the iter mut.2 prepend now skips the iter-and-find walk when
the stack is empty, eliminating any per-Var-resolution overhead
for the common case (programs with no mut blocks). The
short-circuit did NOT close the check_ms regression — see ratify
below.
- Spec docs/specs/2026-05-15-mut-local.md §'Out of scope' amended
with the lambda-capture rejection bullet.
- Negative fixture examples/test_mut_var_captured_by_lambda.ail.json
+ driver test extension in crates/ailang-check/tests/
mut_typecheck_pin.rs (6th test) +
crates/ailang-core/tests/carve_out_inventory.rs EXPECTED bumped
12 → 13.
Bench-regression ratify:
bench/compile_check.py check_ms showed a uniform ~30-50% relative
shift across the entire 11-fixture suite (~0.5ms absolute on a
1.4-1.5ms baseline). The uniformity across fixtures of very
different Var counts argues for a fixed-cost-per-invocation tax,
not a Var-proportional hot path. The Term::Var short-circuit
falsified the hot-path hypothesis. The plausible remaining causes
(synth-parameter-passing through ~19 recursive sites, and binary-
size startup tax from mut-* adding ~1400 LOC to typecheck/codegen)
are both feature-cost, not pathological. Ratified by paired
journal entry; bench/baseline_compile.json updated to the post-
mut-local numbers via 'bench/compile_check.py --update-baseline'.
bench/check.py continues to show the established tail-latency-noise
envelope from audit-pd (2026-05-14) — no separate ratify needed.
bench/cross_lang.py clean.
Tests: 594 → 598 green (4 new lib.rs mod tests + 1 driver test +
1 fixture-corpus uptake).
Journal: docs/journals/2026-05-15-iter-mut.4-tidy.md.
mut-local milestone end-to-end status:
- mut.1 (7b92719): AST + Form A surface.
- mut.2 (b24718a): typecheck.
- mut.3 (03fb633): codegen + e2e.
- mut.4-tidy (this commit): audit-drift close + bench ratify.
mut-local milestone CLOSED.
Refs: docs/specs/2026-05-15-mut-local.md,
docs/plans/2026-05-15-iter-mut.4-tidy.md.
This commit is contained in:
+10
-11
@@ -2380,8 +2380,8 @@ are real surface forms.
|
||||
// Iter mut.1: in-block update of a mut-var. Legal only as a
|
||||
// sub-term of a `Term::Mut` whose `vars` includes a var with the
|
||||
// same `name`. Static type Unit. The lexical-scope rule is enforced
|
||||
// at typecheck (iter mut.2, `mut-assign-out-of-scope`); codegen
|
||||
// relies on it (iter mut.3).
|
||||
// at typecheck via `mut-assign-out-of-scope`; codegen lowers as a
|
||||
// `store` to the var's entry-block alloca.
|
||||
{ "t": "assign",
|
||||
"name": "<id>",
|
||||
"value": Term }
|
||||
@@ -2391,15 +2391,14 @@ In the MVP, `do` is only a direct call to a built-in effect op (no
|
||||
handler). A `lam` term constructs an anonymous function value; free
|
||||
variables of its body are captured from the enclosing scope.
|
||||
|
||||
Iter mut.1 lands `Term::Mut` and `Term::Assign` as first-class AST
|
||||
nodes that round-trip cleanly between Form A and canonical JSON.
|
||||
Typecheck recognition (iter mut.2) and codegen lowering (iter mut.3)
|
||||
are sequenced as separate iterations; in iter mut.1 the typecheck
|
||||
dispatch (`synth` in `ailang-check`) and codegen dispatch
|
||||
(`lower_term` in `ailang-codegen`) stub these variants with
|
||||
`CheckError::Internal` / `CodegenError::Internal` respectively, per
|
||||
spec `docs/specs/2026-05-15-mut-local.md` §"Iteration mut.1"
|
||||
out-of-iteration boundary.
|
||||
Both shapes ship in fully-lowered form as of iter mut.3 (2026-05-15):
|
||||
typecheck binds mut-vars in a lexical scope stack, codegen lowers
|
||||
them as entry-block allocas, and `Term::Assign` emits a `store`
|
||||
yielding the canonical Unit SSA. Mut-vars must be of one of the
|
||||
stack-resident scalar types `{Int, Float, Bool, Unit}`; capturing a
|
||||
mut-var into a lambda body is rejected at typecheck via
|
||||
`CheckError::MutVarCapturedByLambda` (iter mut.4-tidy). See
|
||||
`docs/specs/2026-05-15-mut-local.md`.
|
||||
|
||||
**`Literal`**:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user