6a4e8669a3157e6b9836f375ef178eb11224ffed
114 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
425c4eb3c5 |
audit embedding-abi-m1: milestone close — CLEAN, carry-on, no ratify
Architect clean (Invariant 1 holds semantically; DESIGN.md honesty satisfied; lockstep intact; Design-decision 6 module==stem confirmed correct, not drift). Sole [low] (rustfmt-divergent export: None, indentation) orchestrator-adjudicated carry-on on evidence: cargo fmt --all --check shows 1137 fmt-divergent locations tree-wide predating M1 — rustfmt is not a project convention, the churn premise is moot. Bench: compile_check 0/24 + cross_lang 0/25 exit 0; check.py exit 1 (7 firings: *.bump_s trio + explicit/implicit_at_rc p99_9/max_us). Bencher causal exoneration decisive: 21/21 bench binaries cmp-byte- identical 064599e..e406d07 (Target::Executable default = zero emitted- code change for the bench corpus); latency tails reproduce on the byte-identical oracle itself (-n 5 single-sample jitter); fresh re-run collapsed 6/7 to ok. Both families map to the two pre-existing roadmap-P2 tracked noise items. NO baseline ratify (Iron Law forbids ratifying noise against a zero-default-path-byte iter). M1 audit CLEAN. fieldtest (surface-touching) is the next pipeline step before milestone close. |
||
|
|
e406d07d81 |
iter embedding-abi-m1.1 (DONE 7/7): Embedding-ABI M1 — scalar AILang fn callable from C/Rust
Tasks 4-7 on the Boss-Repaired plan, completing the M1 iteration: - Task 4: check-side export-signature gate — CheckError variants ExportNonScalarSignature / ExportHasEffects (codes export-non-scalar-signature / export-has-effects), code() arms, check_fn gate (params->ret->effects, first-violation-wins, unconditional on f.export.is_some()). The clause-3 discriminator in code: effectful/non-scalar exports fail to typecheck. 6/6. - Task 5: codegen Target::StaticLib — Target enum, threaded param, @main/MissingEntryMain gated behind Target::Executable, one external @<sym> forwarder per export to @ail_<module>_<fn> (fn_scalar_sig/llvm_scalar). In-source missing_entry_main_is_error byte-unchanged. Lowering pin 2/2. - Task 6: CLI ail build --emit=staticlib — clap field, Cmd::Build branch, build_staticlib (verbatim build_to prefix + has_export guard + two-archive clang -c/ar rcs tail) + run_cmd. CLI pin 2/2. - Task 7: C-host E2E coherent-stop proof (cc links libembed_backtest_step.a + libailang_rt.a, backtest_step(0,3)=9 / (9,4)=25, s==25, exit 0) + DESIGN.md §"Embedding ABI (M1)" (scalar C ABI, provisional until M3). E2E pin 1/1. Independent Boss verification: E2E 1/1, gate 6/6, full workspace 0 failures, roundtrip_cli + design_schema_drift + spec_drift green, Invariant 1 holds (no new dep in core/codegen/runtime). 3 behaviour- neutral plan pseudo-vs-reality concerns in the journal. Default-exe codegen/runtime path byte-unchanged (check.py firing = tracked-P2 known-noise, audit-adjudicated). INDEX line added. Completes the M1 iteration (impl). Milestone-close audit + fieldtest (surface-touching) are the next pipeline steps. |
||
|
|
818177d835 |
iter embedding-abi-m1.1 (PARTIAL 3/7): schema + surface + baseline prerequisites; plan Repair
Tasks 1-3 of docs/plans/embedding-abi-m1.1.md, a verified known-good subset (cargo build --workspace exit 0; full workspace green; the roundtrip_cli all-examples gate green): - Task 1: CLI build-path MissingEntryMain baseline pin (examples/embed_noentry_baseline.ail + embed_missing_main_baseline.rs; triple-assertion, layered on the green codegen-unit pin lib.rs:3314). - Task 2: additive FnDef.export: Option<String> (serde default + skip_serializing_if, modelled on FnDef.doc); compile-driven thread of export: None across ~85 FnDef/AstFnDef literals / 18 files incl. all in-source #[cfg(test)] mod tests + drift/hash/spec-drift literals + the codegen lib.rs:3320 in-source pin; hash-stability golden pin; DESIGN.md fn-JSON "export" line. DONE_WITH_CONCERNS (plan symbol content_hash_fn -> def_hash per hash_pin.rs, plan- pseudo-vs-reality class, plan corrected). - Task 3: Form-A (export "<sym>") modifier — parse_export + parse_fn thread + write_fn_def emission + form_a.md grammar; byte-identical round-trip. Task 4 BLOCKED (Boss plan-defect: fixtures declared module != file stem; AILang's loader hard-enforces module==stem at workspace.rs:438, so ail check panicked on load before the gate). Boss resolution (Option 2, overriding the orchestrator's Option 1, rationale in the journal + plan Design-decision 6): keep descriptive embed_* filenames, rename fixture MODULES to their stems — the C symbol backtest_step stays via (export ...), demonstrating spec Decision 2's mangling- decoupling rather than violating it; archive becomes libembed_backtest_step.a, internal symbol @ail_embed_backtest_step_step, host.c unchanged. Plan fixed (Design-decision 6 + Task-4 module==stem + Task-5/6/7 dependent strings + the no-*. Float-head + content_hash_fn concerns folded). Headline fixture corrected; blocked-Task-4 WIP discarded (recreated by the [4,7] re-dispatch from the fixed plan). PARTIAL commit (not the iter's final commit): the implement Repair path mandates committing the known-good subset so the [4,7] re-dispatch starts from a clean tree that includes Tasks 1-3 (Tasks 4-7 structurally depend on the schema field + surface). INDEX line added when the full iter lands post-re-dispatch. |
||
|
|
3fb184417d |
audit docs-honesty-lint: milestone close — CLEAN, carry-on, no ratify
Architect clean (one [medium] advisory Sweep-5 self-match wart filed as P3, not a fix iteration). Bench causally exonerated: HEAD vs 5bb7211/de66eb7 — all 5 firing bench binaries cmp-byte-identical, ail sha256 identical at all 3 commits; the check.py firings are the two tracked-P2 environmental classes (*.bump_s anchor staleness; *.max_us -n5 tail-jitter, 4th on a zero-runtime-change milestone), NOT this milestone — NO baseline ratify (Iron Law forbids ratifying noise). compile_check 0/24, cross_lang 0/25 EXIT 0. No fieldtest (zero authoring-surface change). docs-honesty-lint CLOSED: roadmap [~]->[x], WhatsNew appended, INDEX line added. |
||
|
|
e50b400d4b | journal: INDEX line for iter docs-honesty-lint.1 | ||
|
|
7580d434f0 |
iter docs-honesty-lint.1: canonical docs present-tense-honest + wrap-robust pin + Sweep 5
Strips Wunschdenken (forward intent stated as fact) and non-citation post-mortem / doc-archaeology from docs/DESIGN.md (14 corrections incl. 1 Step-14 procedural-catch-all site the spec's illustrative regex would have missed) + docs/PROSE_ROUNDTRIP.md; genuine forward intent (LLM tool-use / MCP / LSP) relocated to roadmap P3. Codifies the tense+modality discriminator as a present-tense DESIGN.md meta- subsection. Guards two ways: wrap-robust enumerated absent/present pin (crates/ailang-core/tests/docs_honesty_pin.rs, norm() whitespace- collapse helper — structurally discharges the recurring grep/contains line-wrap failure family) + wrap-robust advisory Sweep 5 in architect_sweeps.sh (contiguous-fragment regex) with full sweep-count lockstep + new ailang-architect.md "DESIGN.md honesty drift" bullet. KEEPs preserved (Diverge-reserved, regions-rejected, self-labelled tiebreaker). No language/checker/codegen/runtime change — sole crates/ change is the additive pin; ail check/run/build byte-unchanged. Workspace 609/0 (delta +4 pin), sentinel trio green, build clean. |
||
|
|
5bb721178f |
fieldtest: remove-mut-var-assign — 4 examples, all working; milestone CLOSED
Post-audit downstream-LLM-author field test of the post-removal Form-A surface. 4 fresh real-world programs written from DESIGN.md + form_a.md + public examples only (no compiler source): running sum-of-squares accumulator (loop/recur → 385), grade cascade (let-threaded if → 4/2/0), Horner polynomial straight-line build-up (let-shadow chain → 73), multi-state bracket scanner threading (rest,depth,ok) by tail recursion → true/false. 0 bugs, 0 friction, 0 spec_gap, 4 working — every task clean and correct on the first try with only let/if/loop/recur; all parse|render|parse byte-identical; zero mut/var/assign tokens produced (a doc-faithful author did not reach for the removed construct). The closest-to-friction case (multi-state machine restates the full state tuple per tail-call) is correctly classified working, not a spec_gap: that explicit-dataflow cost is the local-reasoning pillar working as designed; mut's implicit cross-iteration persistence is exactly the failure class the removal eliminates. mut-keyword rejection is fail-closed with a diagnostic that enumerates the surviving forms (no tombstone, no-nostalgia, as spec'd). Removal thesis empirically CONFIRMED — mut was redundant with let/if/loop/recur in 100% of the fresh tasks. The remove-mut-var-assign milestone is fully ratified and CLOSED: spec+plan+iter, audit clean (architect [high] form_a.md fixed in .tidy, bench causally exonerated), fieldtest clean. Roadmap P0 flipped [~]→[x]; WhatsNew user-facing entry appended. |
||
|
|
4837871ddf |
iter remove-mut-var-assign.tidy: form_a.md grammar deletion + audit close
Milestone-close audit for remove-mut-var-assign (48e7774..07f0802).
Architect drift: one [high] — crates/ailang-core/specs/form_a.md
still documented the full mut/var/assign construct (EBNF 286-288,
prose 308-328, dangling `like mut` at 332). The spec named it for
deletion; the iter missed it. Root cause = the recon-undercount
class (4th recurrence) plus a Boss error: a plan-time grep checked
only docs/form_a.md (dir-scoped), got "No such file", and that
unverified non-existence claim propagated into the recon brief —
the real file is crates/ailang-core/specs/form_a.md. A stale doc
never compile-fails so the implement compile-sweep could not catch
it. Fixed inline as a Boss .tidy (CLAUDE.md context-already-loaded
carve-out; full review discipline kept — tree-wide verified zero
construct residue except the roadmap entry naming the milestone
itself; grammar now reads coherently reuse-as -> loop -> recur).
Bench causally exonerated: HEAD vs
|
||
|
|
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 |
||
|
|
48e7774d01 |
audit: prose-loop-binders milestone CLOSE — clean / carry-on
Architect drift review clean (zero drift/debt, diff-verified: single Term::Loop write_term hunk, no non-render site / Term::Recur / doc / lockstep touched). Bench: compile_check 0/24 + cross_lang 0/25 exit 0; check.py's latency-tail firings proven single-sample jitter by an identical confirmatory re-run (p99.9 +37%→+10% ok, max +114%→+22% ok); the sole persistent firing is the pre-tracked P2 *.bump_s environmental staleness, causally impossible to attribute to a projection-only ailang-prose change. All items carry-on — no fix, no baseline ratify. Fieldtest not applicable (no authoring-surface change). Roadmap P0 flipped to closed; WhatsNew user-facing entry appended (user present → notification suppressed per policy). |
||
|
|
c9355d7d58 |
iter prose-loop-binders.1: Form-B loop binders as parenthesised init-list
The Term::Loop arm of write_term rendered binders as bare
`name = init;` statements inside the `loop { … }` body block, which
reads as C/Rust re-init-every-iteration semantics — a projection
lying about a body the prose surface exists to let the reader trust.
Rewrite to a parenthesised init-list on the keyword line,
`loop(acc = 0, i = 1) { … }`, positionally isomorphic to the
unchanged Term::Recur arm. Projection-only: loop/recur AST, Form-A,
JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip
invariant untouched. RED-first via two new committed
examples/*.prose.txt byte-equality snapshots + two snapshot_loop_*
tests; full ailang-prose suite 10/0; both ail-prose CLI byte-matches
green. Single-iteration milestone, brainstorm→plan→implement.
Spec: docs/specs/2026-05-18-prose-loop-binders.md
Plan: docs/plans/prose-loop-binders.1.md
|
||
|
|
2ed355c6fa |
fieldtest: loop-recur — 10 examples, 6 findings (milestone CLOSE, clean on all axes)
Post-audit downstream-LLM-author field test of the shipped loop/recur surface (DESIGN.md + public examples only). 3 real iterative programs (Newton isqrt, Collatz, Euclidean gcd) + 5 plausible-mistake negatives + 2 no-termination probes, all run through the public ail CLI. 0 bugs. 4 working findings on the milestone's own axes: rejection diagnostics point-exact AND self-fixing; recur tail-position threads through match/let/outer-if (spec only showed if); loop composes as a value sub-expression + byte-stable round-trip; no-termination boundary exact. This empirically substantiates the "LLM author can now write iterative programs" claim. Two orthogonal non-blocking findings, neither in loop/recur scope, both routed to P2 todos (refused the scope creep into a loop/recur tidy): niladic (app f) spec_gap independently re-confirms the existing mut-local-F3 roadmap item (the design-fork decision deliberately NOT auto-ratified under /boss — parked, priority-strengthened); module-level (doc) diagnostic-hint friction (one-line tidy). Boss-verified independently (gcd->27; recur-outside-loop fires exact). The standalone loop/recur milestone is fully ratified and CLOSED: 3 iterations + tidy shipped, audit clean (drift resolved, bench pristine carry-on), fieldtest clean on every axis. Roadmap P0 marked closed. |
||
|
|
2ee97943bd |
iter loop-recur.tidy GREEN: reject lambda-captures-loop-binder at check + milestone-close audit resolution
GREEN side of the RED audit-trail commit
|
||
|
|
edd2558d35 |
iter loop-recur.3: codegen — real LLVM-IR lowering + run-to-value E2E (milestone terminal)
Third and terminal iteration of the standalone loop/recur
milestone (plan
|
||
|
|
1566ce0b29 |
iter loop-recur.2: typecheck semantics — binder typing, recur checks, verify_loop_body
Second of three iterations of the standalone loop/recur milestone
(plan
|
||
|
|
a179ec30a0 |
iter loop-recur.1: additive Term::Loop / Term::Recur / LoopBinder foundation
First of three iterations of the standalone loop/recur milestone
(spec
|
||
|
|
a29700cc9e |
iter effect-doc-honesty: make the effect-system documentation true
Standalone documentation-honesty tidy (no language/checker/codegen change; `ail check`/`run` byte-unchanged by construction). Corrects three false effect-system claims the effect-subsystem recon surfaced, plus two satellite mentions, guarded by a new doc-presence pin: - DESIGN.md Decision 3: removed the "row-polymorphic (`![IO | r]`)" claim (no EffectRow / row variable exists in any crate — effect sets are a flat, unordered, closed set unified by set-equality); reconciled "`IO` and `Diverge` are wired up" to IO-only + `Diverge` reserved/unimplemented (zero code in any crate), modelled on Decision 4's reserved-refinements precedent. - DESIGN.md "What is not (yet) supported": "IO and Diverge ops" bullet -> IO-only + Diverge-reserved. - ast.rs Term::Do doc-comment: "resolved against the effect-handler table at link time" -> the real mechanism (typecheck lookup in Env::effect_ops + literal lower_effect_op codegen match; no handler table, no link-time resolution). - form_a.md:226 + rule 3, and main.rs merge-prose CONTRACT example: Diverge mentions reconciled in lockstep. - new crates/ailang-core/tests/effect_doc_honesty_pin.rs: 4 tests, fiction-absent + corrected-anchor-present, single-line wrap-robust substrings. cargo test --workspace 600 -> 604 (4 new pin tests, 0 regressions); design_schema_drift / spec_drift / schema_coverage stay green, empirically confirming none scans the effect-prose region. |
||
|
|
78426643df |
audit: iteration-discipline-revert milestone close — clean
Architect: revert byte-pristine vs |
||
|
|
37ac704bf3 |
iter revert: back out the Iteration-discipline milestone (it.1 + it.2)
One forward iteration; main never rewound. |
||
|
|
305ffd1367 |
iter it.3 BLOCKED: surfaced fundamental milestone-design fork (bounce-back)
it.3 Task 1 (non-destructive oracle + spec-delegated class-(b) sweep) ran complete; zero production code changed. The mandated sweep surfaced that 6 corpus fixtures contain build(d:Int)=if d==0 then Leaf else Node(1,build(d-1),build(d-1)) — a terminating, maximally-LLM-natural, non-structural non-tail BRANCHING recursion the milestone's totality dichotomy makes inexpressible (not ADT-structural; not recur-able — branching is not a single tail back-edge). The milestone as-specified would fail its own feature-acceptance criterion. Orchestrator correctly bounced rather than patch a 4th time (spec_over_plan_patches). Records the blocked attempt + the reusable bench/it3-oracle/ (40-fixture behavioural oracle, valid for the post-decision re-dispatch). main forward-only; nothing reverted. Resolution (it.2b + a user purity-pillar decision) tracked in the roadmap. |
||
|
|
a4be1e58a3 |
iter it.2: structural-guardedness checker + first real Diverge effect
Iteration-discipline milestone, 2 of 3. Strictly additive (nothing tail-related removed; that is it.3). New whole-body pass verify_structural_recursion sibling of verify_tail_positions (DD-1): smaller-set algorithm with implicit candidate inference + unconstrained accumulators (DD-2, foldl=structural), self/mutual via inline ADT-family union-find (DD-3), it.2-only tail==false grandfather. CheckError::NonStructuralRecursion. term_contains_loop (stops at Term::Lam, DD-4) injects Diverge so existing UndeclaredEffect enforces it, no new variant; lam-arrow + LetRec sub-effect sites wired. DESIGN.md Decision 3 synced. Four it.1 loop fixtures gained !Diverge. Two spec-premise boundary defects surfaced + resolved within the additive invariant (corpus clean, check not weakened), recorded as corrected it.3 corpus-migration scope: (1) the "21 tail-app fixtures" grandfather premise under-counts the corpus — no-ADT-candidate counter recursions have no structural position to verify, deferred to it.3; (2) two RC-regression fixtures joined the spec's transitional tail-app grandfather as the other 20 do (RC==GC guards verified still green). cargo test --workspace 622/0; 9 acceptance pins non-vacuous. Spec |
||
|
|
96db54d15d |
iter it.1: loop/recur additive — Term::Loop/Term::Recur/LoopBinder end-to-end
Iteration-discipline milestone, 1 of 3. Adds named loop + recur as strictly-additive first-class AST nodes: parse/print/prose/serde/ round-trip/schema lockstep, typecheck (binder typing + recur arity/type unification via loop_stack threaded as mut.2's mut_scope_stack; recur-tail-position via verify_loop_body), codegen (loop-header + one phi per binder; recur back-edge br with a NEW parallel block_terminated setter; lambda-boundary loop_frames save/restore). Four Recur* CheckError variants. Strictly additive: zero deletions touch tail-app/tail-do or the seven existing block_terminated sites — this is what makes the destructive it.3 safe. recur synth = fresh metavar (resolves the plan's flagged Type::unit() risk). loop_counter->55, loop_in_lambda->49, four negatives fire, tail-app byte-identical, cargo test --workspace green. Spec |
||
|
|
1ff7e811af |
fix: mut-local diagnostics doubled the [code] prefix in human stderr (fieldtest F2)
The four mut-local CheckError variants (MutAssignOutOfScope, AssignTypeMismatch, UnsupportedMutVarType, MutVarCapturedByLambda) embedded the bracketed kebab code in their thiserror Display body, while the non-JSON CLI formatter independently prepends [code] from CheckError::code() — doubling it. Non-mut variants never embedded the bracket; this brings the four in line with that convention. RED-first via the debug skill: ct1_check_cli.rs pins the observable property (code appears exactly once in the rendered human diagnostic). GREEN was a trivial four-string mechanical edit. Tests 598 -> 599. |
||
|
|
20add51112 |
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 (
|
||
|
|
03fb633d85 |
iter mut.3: codegen + e2e — mut-local milestone end-to-end closed
Replaces the iter mut.1 dispatch stubs in lower_term with real LLVM
lowering: mut-vars become alloca slots hoisted to the fn's entry
block, assign lowers to store, mut-var reads lower to load. Two
example fixtures run end-to-end printing 55 (Int via mut_counter,
Float via mut_sum_floats).
Concretely:
- Emitter struct gains three new fields: mut_var_allocas:
BTreeMap<String, (String, Type)> for per-fn name→(alloca SSA,
AIL type) tracking; pending_entry_allocas: String as a side
buffer accumulating alloca instructions during body lowering;
entry_block_end_marker: Option<usize> recording the byte position
in self.body immediately after the entry: label.
- start_block(label) captures the marker when label == 'entry'.
emit_fn resets all three new fields per fn body. At the end of
emit_fn, String::insert_str splices pending_entry_allocas into
self.body at the marker — alloca instructions land in the entry
block regardless of where in the source tree Term::Mut was
encountered (mem2reg eligibility preserved).
- Term::Mut arm: per var, fresh-name an alloca SSA, push the
alloca instruction into pending_entry_allocas, lower the init at
the current body position, emit a store to bind. The mut-var
binding is NOT visible during init (matches typecheck-side
ordering at lib.rs:3576). After all vars are bound, lower the
body in the extended scope. On block exit, restore any prior
bindings via a per-block save stack — supports nested
shadowing correctly.
- Term::Assign arm: look up the alloca + type via
mut_var_allocas.get(name); lower the value; emit store; yield
the canonical Unit SSA per the existing Term::Lit { lit:
Literal::Unit } convention.
- Term::Var arm: prepended with a mut-var lookup that emits a
load <ty>, ptr <alloca> and returns the load SSA. Innermost-wins
shadowing falls out of the BTreeMap's insert-overwrites
semantics combined with the per-block save/restore.
- examples/mut_counter.ail + examples/mut_sum_floats.ail:
recursive sum_helper accumulates 1..10 (Int) or 1.0..10.0 (Float)
inside a mut block whose single var is assigned the helper's
result. Both run end-to-end printing 55.
- crates/ail/tests/e2e.rs: mut_counter_prints_55 and
mut_sum_floats_prints_55 #[test] fns using the existing
build_and_run helper.
- DESIGN.md 'What is supported' subsection: 'Local mutable state'
bullet describes the new construct, points at the two example
fixtures, and reaffirms the seal-by-construction invariant under
Decision 10.
Beyond-plan adjustments absorbed in this iter:
- synth_with_extras's parallel Term::Var arm in
ailang-codegen/src/lib.rs needed the same mut-var lookup as
lower_term's. Plan only specified lower_term's arm.
- crates/ailang-codegen/src/lambda.rs needed save/restore of all
three new Emitter fields across the lambda-body boundary plus
in-thunk splice of pending_entry_allocas at the lambda's own
entry marker, so mut-blocks inside a closure body hoist into
the closure's entry block (not the outer fn's).
mut-local milestone end-to-end status:
- mut.1 (
|
||
|
|
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.
|
||
|
|
7b92719244 |
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.
|
||
|
|
354175be30 |
audit-pd: tidy items + milestone close (prelude-decouple)
Architect drift: 1 Important + 1 Minor fixed inline as audit-pd-tidy.
DESIGN.md §"Roundtrip Invariant" point 4 + §Enforcement asserted
"Eight `.ail.json`-only fixtures" with one being the prelude embed.
Post-pd.3 the count is seven and the embed clause is empty; rewrote
both paragraphs to reflect the post-milestone state with a forward-
pointer to the prelude-decouple resolution.
ail/src/main.rs's migrate-canonical-types subcommand carried a
`local_types.get("prelude")` fallback in `rewrite_type` that was
dead-by-construction after pd.3 retired the synthetic prelude load
in the same subcommand. Removed the or_else arm; replaced with a
multi-line comment naming the milestone and the repopulation path
for any future variant that needs prelude awareness.
Bench: check.py + compile_check.py exit 1 with the established
envelope-noise (15th consecutive observation since audit-cma);
cross_lang.py clean. pd.* iterations are workspace-loader / spec /
filesystem / test-code only — zero codegen / runtime / typecheck
path edited, so the regression cluster cannot be milestone-induced.
Baseline left pristine consistent with all 14 prior decisions.
Roadmap: P0 entry (prelude-decouple, [~]) struck and removed; P0 is
empty again. The pre-milestone P2 entry that this work superseded
("Prelude embed: Form-A as compile-time source") is gone too. Per-
iter journals stay in INDEX.md as chronological context.
Carried debt unchanged: 3 cargo doc warnings on dangling load_workspace
references in core's lib.rs (covered by the existing P2 "Rustdoc
warning sweep" todo).
|
||
|
|
9a8d3850e7 |
iter pd.3: prelude.ail.json retired — milestone prelude-decouple closed
Deleted examples/prelude.ail.json (-559 lines). The cross-form-identity preflight test that ratified pd.3's load-bearing assumption at module hash 3abe0d3fa3c11c99 has discharged its purpose and was removed along with its supporting bytes; the long-term prelude_parse_yields_canonical_hash anchor stays. The ail-CLI migrate-bare-cross-module-refs subcommand's defensive include + lockstep skip-branch removed; the rewrite logic's prelude-fallback capability silently retired with the synthetic insert (known debt, doc-comment updated to reflect). One in-mod core test relocated to crates/ailang-core/tests/workspace_pin.rs (pd.2.4 dev-dep cycle precedent — in-mod call to ailang_surface still structurally impossible). carve_out_inventory.rs: 8 → 7 carve-outs, §C4(b) row dropped, header sentence updated. form-a-default-authoring.md §C4(b) gets a "RETIRED 2026-05-14 by milestone prelude-decouple" status marker; original historical text preserved. New prelude_decouple_carve_out_pin.rs asserts examples/prelude.ail.json does NOT exist. Milestone prelude-decouple closed: prelude exists on disk only as examples/prelude.ail; ailang-core embeds zero prelude bytes; CLAUDE.md "authors write .ail" doctrine holds without exception. cargo test --workspace 573 green; bench/cross_lang.py + bench/compile_check.py exit 0; bench/check.py exit 1 with the established noise envelope (14th observation of bench_list_sum.bump_s since audit-cma; pd.3 is filesystem + spec text + test deletion only, no codegen / runtime / typecheck path edited — baseline pristine). Folds in the orphan pd.2 INDEX entry that was left uncommitted in the working tree. |
||
|
|
008d18bb18 |
iter pd.2: surface owns prelude embed; pd.1 shim retired
Moved PRELUDE_AIL const + parse_prelude fn to ailang-surface; re-exported from surface's lib.rs. surface::load_workspace rewritten 3-line shim-call → 7-line composition: load_modules_with → reserved-name check → inject parse_prelude → build_workspace(&["prelude"]). Five deletions in core: PRELUDE_JSON, load_prelude, load_workspace_with (the pd.1 shim), the top-level cfg(test) load_one fn, and the cfg(test) crate::load_module import. Production literal-"prelude" count in crates/ailang-core/src/ is now zero. Cross-form-identity preflight PASSED at module_hash 3abe0d3fa3c11c99 — parse(prelude.ail) ≡ deserialize(prelude.ail.json), so pd.3's deletion of the JSON is safe. Three new pin tests in ailang-surface/tests/: prelude_parse_pin (succeeds + min defs); prelude_module_hash_pin (cross-form-identity + literal-hex anchor); prelude_injection_pin (inject + reservation). Plan deviations (recorded in journal): switched the regression-pin fixture from non-existent examples/hello.ail.json to hello.ail; relocated 10 in-mod core tests to crates/ailang-core/tests/workspace_pin.rs because the dev-dep cycle (core ↔ surface) prevents in-mod aliasing in the lib-test target (form-a.1 T5 precedent); preserved load_one production-symbol-deletion by adding a test-mod-private load_one helper in core's mod tests for the 2 pd.1-introduced tests still consuming it. cargo test --workspace 573 green (+9 net from pd.1 baseline). bench/cross_lang.py + bench/compile_check.py exit 0; bench/check.py exit 1 with established noise envelope (bench_list_sum.bump_s, 13th consecutive observation; pd.2 is workspace-loader-only, no codegen touch — baseline pristine). |
||
|
|
ade7464937 |
INDEX + roadmap: iter pd.1 logged; prelude-decouple milestone moved P2 → P0 [~]
Original "Prelude embed: Form-A as compile-time source" P2 entry removed; superseded by the wider "Prelude / core decoupling" P0 entry that now reflects the shipped β.2 loader-split shape and the three- iter sketch (pd.1 done, pd.2 + pd.3 to follow). |
||
|
|
ffa80326a3 |
iter pd.1: core API split — load_modules_with + build_workspace + implicit_imports threading
Carved load_workspace_with's inline DFS+inject+validate block into two new public fns: load_modules_with (DFS only) and build_workspace (validation + registry, takes implicit_imports: &[&str]). Threaded the new parameter through validate_canonical_type_names + check_class_ref + check_type_con_name; the four hardcoded literal-"prelude" fallback blocks in the diagnostic helpers became iter-loops over implicit_imports. load_workspace_with survives as a 3-line shim composing the new fns + the still-in-place prelude inject, so surface (ailang-surface) and all downstream crates compile + test unchanged. Retired pub fn load_workspace (zero production callers; 9 in-mod test callers migrated to load_workspace_with(&entry, load_one); load_one + the load_module import gated under cfg(test) since they're now test-only). Production literal-"prelude" count in workspace.rs dropped 8 -> 4 (all 4 in the shim's inject path; none in the diagnostic helpers, which was the spec's pd.1 target). pd.2 will move the prelude embed + the inject step into ailang-surface and retire the shim; pd.3 will delete examples/prelude.ail.json. cargo test --workspace green; bench/check.py + compile_check.py + cross_lang.py all exit 0. |
||
|
|
b8a5a4a10c | INDEX: iter cli-diag-human — bracketed [code] in non-JSON stderr | ||
|
|
f08ba2bb36 |
iter cli-diag-human: route WorkspaceLoadError through diagnostic in non-JSON path
Closes P2 todo "CLI human-mode diagnostic surface for
WorkspaceLoadError" surfaced by ct.1.8 tester (2026-05-11).
Pre-iter, non-JSON `ail check` (+8 sibling subcommands) propagated
loader errors via anyhow's Display, producing
`Error: <message>` without the bracketed `[code]` prefix the JSON
path emits.
Fix: new private helper `load_workspace_human(path)` in
crates/ail/src/main.rs that wraps ailang_surface::load_workspace,
threads any WorkspaceLoadError through workspace_error_to_diagnostic,
formats the resulting Diagnostic into a single stderr line
`error: [<code>] <def>: <message>`, and exits non-zero. Pure I/O
errors still propagate as anyhow errors.
Nine call sites swapped to the helper (check non-JSON arm, build,
run, emit-ir, prose, describe, deps, diff, manifest). The JSON arm
of `ail check` keeps its explicit `match` — different output
contract (structured stdout array).
The BareCrossModuleTypeRef Diagnostic message gained the existing
migration-command hint ("Run `ail migrate-canonical-types` to fix
legacy fixtures.") so the ct.1.8 actionable-hint test stays green.
The hint previously rode on WorkspaceLoadError's thiserror Display;
making workspace_error_to_diagnostic the single source for both
JSON and human paths required moving it into the Diagnostic body.
RED test crates/ail/tests/cli_diag_human_workspace_load_error.rs
pins the bracketed-code surface on a missing-import workspace
(tempdir-based fixture; no committed corpus addition).
cargo test --workspace: 565/0/3. clippy + doc: zero warnings.
Roadmap entry struck.
|
||
|
|
6755060175 | INDEX: iter rpe.1.tidy — audit-rpe.1 [medium] drift closed | ||
|
|
21d821e371 |
iter rpe.1.tidy: subst.rs preserves Type::Fn modes through rebuild
Closes audit-rpe.1's [medium] drift: qualify_local_types_codegen
and apply_subst_to_type at crates/ailang-codegen/src/subst.rs:188 +
:217 both carried the field-spread Type::Fn rebuild shape that
strips param_modes and ret_mode to vec![] / ParamMode::Implicit.
Symmetric to the
|
||
|
|
973f50bf68 | INDEX: iter rpe.1 — retire per-type print effect-ops, milestone closed | ||
|
|
6fdb45d2f2 |
iter rpe.1: retire per-type print effect-ops
Single iter shipping the post-milestone-24 follow-up named in
docs/specs/2026-05-14-retire-per-type-print-effects.md. After this
iter the only surviving direct-output effect-op is `io/print_str`;
all per-type print primitives are replaced by the polymorphic
`print` helper (prelude, iter 24.3).
Components:
- 92 examples/*.ail fixtures migrated (do io/print_<T> x) →
(app print x); 6 .prose.txt snapshots regenerated via `ail prose`.
- Four-site lockstep compiler deletion: crates/ailang-check/src/builtins.rs
(3 effect_ops.insert blocks + 3 list() rows + the
install_io_print_float_signature test + module + EffectOpSig
doc-comments); crates/ailang-codegen/src/lib.rs lower_app
(3 arms + lowers_io_print_float test); crates/ailang-codegen/src/synth.rs
builtin_effect_op_ret match-arm pattern. Dead `intern_string`
helper removed as a follow-up.
- Five incidental test-body migrations (ailang-check x2, ailang-core
spec_drift + design_schema_drift, ailang-surface/src/lex.rs,
ailang-prose/src/lib.rs round-trip test).
- Cat B test-harness patch: six IR-shape tests in
crates/ail/tests/e2e.rs gained a monomorphise_workspace call
before lower_workspace_with_alloc so they follow the same
pipeline as `ail build` (the home-rolled desugar+lift loop
stayed because mono's precondition is "already lifted"; mono
inserts after lift).
- Six doc-comment touch-ups (lex.rs module doc, parse.rs
diagnostic example, ail/src/main.rs x2, runtime/str.c %g anchor,
crates/ailang-core/specs/form_a.md surface-spec example).
- DESIGN.md seven-site sweep (Decision 11 example, Polymorphic
print past-tense, Heap-Str output sentence, effect-op
invocation comment, Float NaN paragraph re-anchored on
float_to_str, two "What is supported" lists).
- Three E2E test-comment polish + four IR-snapshot refresh + one
canonical-hash pin update (plan-unanticipated downstream
consequences of the corpus migration).
- bench/{check,compile_check,cross_lang}.py: all exit 0; no
ratification needed.
- Roadmap entry struck through; per-iter journal at
docs/journals/2026-05-14-iter-rpe.1.md.
Tests 564/0/3. cargo clippy and cargo doc: zero warnings.
Two upstream codegen bugs surfaced during the first BLOCKED
attempt and were fixed in separate iters before this retry:
-
|
||
|
|
34a33472cb | INDEX: iter bugfix-print-leak-show-ret-mode | ||
|
|
feb941363a |
bugfix: print leak — propagate ret_mode through rigid substitution
`(app print x)` under --alloc=rc leaked the heap-Str allocated by
`show x` in print's body: the let-binder `s` in
`(let s (app show x) (do io/print_str s))` was never flagged
trackable, so the drop site at scope-close emitted no
`ailang_rc_dec(s)`. Minimal repro `(body (app print 42))` produced
`allocs=1 frees=0 live=1`.
Root cause is two-layer:
1. examples/prelude.ail.json declared the Show class method `show`
without an explicit `ret_mode` on the return type, so serde
defaulted to ParamMode::Implicit. Fix: add `"ret_mode": "own"`
on the Show.show method, parallel to how the heap-Str-producing
builtins (int_to_str, bool_to_str, float_to_str, str_clone,
str_concat) declare it. examples/prelude.ail regenerated via
`ail render` to stay parse-isomorphic with the JSON.
2. crates/ailang-check/src/lib.rs `substitute_rigids` was silently
stripping `param_modes` and `ret_mode` whenever it rebuilt a
`Type::Fn`, so even after the prelude fix, the mono-synthesised
`show__Int / Bool / Str / Float` lost the `Own` annotation
during rigid substitution. Fix: preserve both fields through
the substitution.
RED pin: crates/ail/tests/print_no_leak_pin.rs (test
`alloc_rc_print_int_does_not_leak_show_result_str`) + fixture
examples/print_int_no_leak_pin.ail asserts allocs == frees and
live == 0 for `(body (app print 42))` under --alloc=rc.
Ten mono-body hash pins re-recorded (eq__Int/Bool/Str,
compare__Int/Bool/Str, show__Int/Bool/Str/Float, eq__IntBox) —
the bodies are semantically identical; the canonical JSON now
carries the previously-stripped mode metadata, so the body hash
moves. Re-pinning is bookkeeping for the intentional drift, not
a workaround.
Surfaced 2026-05-14 during the rpe.1 BLOCKED orchestrator run
(Cat A). Existed in latent form since iter 24.3 (when Show + print
shipped); only became user-observable when `(app print ...)` joined
the corpus. cargo test --workspace: 565 / 0 / 3.
Open follow-up flagged for next /audit: grep for `Type::Fn { ..., .. }`
field-spread sites — any other shape that drops modes during
rebuild is a latent instance of the same bug class. Out of scope
for this minimal fix.
|
||
|
|
301cbc33a0 | INDEX: iter bugfix-mono-cursor-print-with-class-method-arg | ||
|
|
1fb225ee25 |
bugfix: mono cursor misalignment at poly-free-fn Var with class-constrained Forall
When `synth` visits a `Term::Var v` where v is a polymorphic free fn whose `Type::Forall` carries class constraints (e.g. `print : forall a. Show a => ...`, `lt`/`le`/`gt`/`ge : forall a. Ord a => ...`), it pushes BOTH a class `ResidualConstraint` for each constraint AND a `FreeFnCall` observation at the same Var site. The mono walkers (`interleave_slots` collection-side and `rewrite_mono_calls` rewrite-side) consumed only ONE slot per such Var, leaving the class-residual cursor misaligned for every subsequent class-method call in the same body. The next `eq`/`compare` Var in the body then consumed the leftover `Show T` / `Ord T` slot and was mis-rewritten — codegen reported `call prelude.show__Bool arg type mismatch: expected i1, got i64` (or the Int dual). Fix: new `poly_free_fn_constraint_counts_for_module` registry in mono.rs (sibling to `poly_free_fn_names_for_module`) maps each synth-visible poly-free-fn name to its declared constraint count. Threaded into `collect_residuals_ordered`, `interleave_slots`, and `rewrite_mono_calls`. Both walkers now advance the cursor by 1 + N (FreeFn slot + N class-residual fillers) at every poly-free-fn Var. RED test `print_with_class_method_arg_does_not_misalign_mono_cursor` (crates/ail/tests/show_print_e2e.rs) + fixture examples/print_eq_arg_repro.ail pin the bug — body `(app print (app eq 1 2))` previously crashed at codegen, now prints "false". Bug surfaced 2026-05-14 during the rpe.1 BLOCKED orchestrator run; existed in latent form since iter 24.3 (the poly-free-fn-with-class- constraint synth shape was new there). No schema / codegen / DESIGN.md changes. cargo test --workspace: 564 / 0 / 3. |
||
|
|
abcdd05991 |
iter clippy-sweep: clear all 61 cargo clippy warnings
Hygiene sweep across the workspace under `cargo clippy --workspace
--all-targets`. Before: 61 warnings. After: zero. Tests stay 563
green, `cargo doc` stays at zero warnings, and all three bench
scripts exit 0 against existing baselines.
Twelve lint classes, three fix shapes:
- Documentation hygiene (~32 hits): `doc_lazy_continuation` resolved
by adding blank lines before continuation paragraphs or rephrasing
the offending line; plus four `empty_line_after_doc_comments` hits
in workspace.rs where eight `///` blocks left orphan by form-a.1
T5 test relocation were converted to plain `//`.
- Idiomatic refactors (~16 hits): `.err().expect()` → `.expect_err()`,
redundant `.into_iter()` and `.into()` removed, `|f| g(f)` →
`g`, `.trim().split_whitespace()` → `.split_whitespace()`,
`push_str("x")` → `push('x')`, two manual `impl Default` flipped
to `#[derive(Default)]` + `#[default]`, two nested `if let Some(_)
= …` collapsed.
- Block extraction (1 hit): a 13-line block inside an `else if`
condition in synth's Var-arm extracted to a new helper
`is_class_method_dispatch(name, env)` next to
`qualifier_is_class_shape`.
Three `#[allow]`s with inline rationale where clippy's suggestion
would lose meaning: `only_used_in_recursion` on walk_pattern (the
parameter preserves the five-fn walker-framework signature
uniformity), 2× `if_same_then_else` on qualify_local_types shapes
(two branches encode semantically distinct disqualification
reasons), `too_many_arguments` on `Emitter::new` (8 workspace-flat
tables; bundling would just rename boilerplate).
|
||
|
|
b638abf1e2 |
tidy: rustdoc-sweep + drift-test-narrowing — autonomous batch
Two unrelated hygiene iters bundled because they shipped together in one autonomous-while-Boss-away batch: - iter rustdoc-sweep: cleared all 23 `cargo doc --workspace --no-deps` warnings (17 in ailang-check + 4 in ailang-core + 2 in ailang-surface). Two warning classes: pub-doc-comment links to pub(crate)/private items (15 hits — replaced with plain backtick-code-spans), and unresolved/ambiguous links (8 hits — fully-qualified, disambiguated to fn-form, or escaped). No behaviour change; tests 562 → 562. - iter drift-test-narrowing: design_schema_drift.rs now scans §"Data model" only via new helper data_model_section(), instead of full DESIGN.md. Closes the audit-form-a-precursor [high] "anchor-elsewhere-passes-silently" failure mode. All 38 anchors verified pre-edit to live in §"Data model" so the tightening doesn't regress to red. New pin data_model_section_is_bounded guards the extractor against silent regression. Tests 562 → 563. |
||
|
|
fa1b4962f5 |
INDEX: iter str-concat — heap-Str concatenation primitive in four-site lockstep
Append one-line pointer to the new per-iter journal. Closes the fieldtest-form-a queue: bug (bugfix-instance-body-unbound-var), spec_gaps 2+3 (form-a.tidy), friction (str-concat). |
||
|
|
e7e67e1a40 |
iter str-concat: heap-Str concatenation primitive in four-site lockstep
Closes fieldtest-form-a friction finding #4. `str_concat : (borrow Str, borrow Str) -> Str` ships in the four-site-lockstep pattern established by `str_clone` / `int_to_str` / `bool_to_str` (iter 24.1). The LLM-natural Show-MyType body `(app str_concat "label=" (app int_to_str x))` now parses, checks, builds, and runs end-to-end. Sites touched (lockstep): - runtime/str.c — `ailang_str_concat(a, b)` slab-allocates and memcpys both source payloads into a new heap-Str. - ailang-check/src/builtins.rs — `env.globals.insert("str_concat", Fn { 2x Str borrow, ret Str own, effects [] })` + `list()` row + `install_str_concat_signature` unit test. - ailang-codegen/src/lib.rs — `declare ptr @ailang_str_concat(ptr, ptr)` extern + `lower_app` arm after str_clone + `is_builtin_callable` extension + IR-pin unit test `str_concat_emits_call_to_ailang_str_concat`. - examples/show_user_adt_with_label.ail (new) + crates/ail/tests/ str_concat_e2e.rs (new) — corpus fixture exercising the LLM-natural Show body shape + E2E pin asserting check + build + run produce `Item 42\n`. Lockstep collision repaired: examples/bug_unbound_in_instance_method.ail used `str_concat` as its UNBOUND name (because that was the literal fieldtester repro). Renamed to `format_label` (LLM-author-realistic helper name that will never become a builtin) and updated the pin test `crates/ail/tests/unbound_in_instance_method_pin.rs` accordingly, preserving the regression guard's intent (instance-method-body walked through unbound-var check). DESIGN.md amended: new §"Heap-Str primitives" subsection between the milestone-24 Show-backer enumeration and the existing `Primitive output goes through ...` paragraph, cataloguing all five heap-Str primitives (`int_to_str`, `bool_to_str`, `float_to_str`, `str_clone`, `str_concat`) with signatures, iter origins, and the user-visible-vs-prelude-internal distinction. Show-backer block unchanged. IR snapshots regenerated (hello, list, max3, sum, ws_main) to absorb the new `declare ptr @ailang_str_concat(ptr, ptr)` line in the unconditional extern header — same upkeep pattern as hs.4 which regenerated the same 5 snapshots for the same reason (unconditional declares dead-stripped by clang -O2 when unused). Tests: 559 + 3 = 562 green (E2E pin + builtin-signature test + IR-pin test). Zero re-loops across all 7 tasks. |
||
|
|
0e152c9527 |
INDEX: iter form-a.tidy — form_a.md class/instance/constraints + 3 documentary drift items
Append one-line pointer to the new per-iter journal. |
||
|
|
e809f45e67 |
iter form-a.tidy: form_a.md class/instance/constraints + 3 documentary drift items
Six-task post-fieldtest documentary tidy. No production-code behaviour
changes; 559 tests green at every per-task gate.
T1-T3: form_a.md additions
- §Definitions intro "Three kinds" -> "Five kinds".
- New `### Class — (class ...)` subsection: EBNF carries optional
superclass (0 or 1 per ClassDef.superclass schema), method
signatures with optional defaults; anchored to
examples/test_22c_user_class_e2e.ail (ok 24/2).
- New `### Instance — (instance ...)` subsection: EBNF carries the
(method NAME (body LAM-TERM))* shape; canonical-form CLASS-REF
rule explicitly stated (bare same-module / qualified cross-module);
two examples — same-module abbreviated from
mq3_class_eq_vs_fn_eq_classmod.ail and cross-module qualified
from show_user_adt.ail; bare-cross-module-class-ref diagnostic
named inline.
- §Types `(forall ...)` line extended with optional `(constraints
(constraint CLASS-REF TYPE)+)?` clause; explanatory paragraph
added with no-instance diagnostic anchor; fifth example added
to the Examples block anchored to cmp_max_smoke.ail.
T4: docs/specs/2026-05-13-form-a-default-authoring.md "seven
carve-outs" → "eight carve-outs" at 6 sites contradicting the
§C4(b) compile-time-embed amendment (commit
|
||
|
|
1e20b18eba |
INDEX: iter bugfix-instance-body-unbound-var — instance method bodies walked through check_fn
Append one-line pointer to the new per-iter journal. |
||
|
|
77f584abbb |
iter bugfix-instance-body-unbound-var: GREEN — walk instance method bodies through check_fn
Fixes the bug RED-pinned in
|