From 20b412342d5cf5dc3bf301407a83baa56124d3b9 Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 7 May 2026 19:39:44 +0200 Subject: [PATCH] =?UTF-8?q?Iter=2016a-aux=20=E2=80=94=20DESIGN.md=20drift?= =?UTF-8?q?=20audit=20(post-15f)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After six feature iters since the last docs sweep, DESIGN.md had visible drift. Patched in place rather than queueing the next codegen-heavy iter against a stale spec. DESIGN.md changes (+72 LOC): - Decision 6: form (A) marked shipped (Iter 14c, sole projection since 15e); body kept as audit trail. - Pipeline: desugar pass added between resolve+hash and typecheck; invariant noted that CheckedModule.symbols hashes from the original module, not the desugared one (so ail diff/manifest preserve on-disk identity). - CLI: added deps, diff, workspace, builtins (shipped earlier but never doc'd). - "What is not (yet) supported": re-anchored from "end of Iter 13" to "as of Iter 16a"; removed lifted gates (cross-module ADTs, no-GC, flat-pattern-only); added tighter follow-up gates (literal sub-patterns, local recursive let). - "What IS supported": promoted nested Ctor patterns (16a), cross-module ADTs (14h), form-(A) text surface (14b/14c/15e), Boehm GC (Decision 9 / 14f) into the smoke-test list. - Smoke tests: added std_list_demo, std_maybe_demo, std_either_demo, std_pair_demo, nested_pat fixtures. JOURNAL: 16a-aux entry recording the drift sites and what was explicitly *not* changed (Goal, Decisions 1-5, 7-9, Mangling, Convention, Data model, Verification — spot-checked, all current). Tests: 93/93 unchanged (doc-only). Build clean. --- docs/DESIGN.md | 116 +++++++++++++++++++++++++++++++++++++++--------- docs/JOURNAL.md | 74 ++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 22 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 846f913..97c9e5d 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -113,10 +113,20 @@ Rationale: Trade-off: no inline optimisations through the LLVM API. We rely on `clang -O2` as the standard pipeline. -## Decision 6: authoring surface (Iter 14b — WIP) +## Decision 6: authoring surface (Iter 14b) -**Status: design pass in progress.** Reading without skipping the JOURNAL -will leave this section ahead of the implementation. +**Status: shipped.** Form (A) was chosen in Iter 14b and implemented as +the `ailang-surface` crate (parser + printer) in Iter 14c. Form-A is +gated against drift by `ailang-surface/tests/round_trip.rs`, which +parses every `.ailx` fixture, prints it back, re-parses, and demands +canonical-byte equality. In Iter 15e, `ail render` and both branches +of `ail describe` were rewired to use `ailang_surface::print`, making +form (A) the **sole** text projection of a module — the legacy +non-round-tripping pretty-printer code in `pretty.rs` was deleted in +the same iter, leaving only diagnostic helpers (`type_to_string`, +`pattern_to_string`, `manifest`) public. The rest of this section +records the *why* of Decision 6 for the audit trail; the constraints +listed below describe the surface as shipped. ### Why this is opening up @@ -689,23 +699,41 @@ conversion in JOURNAL). A `seq` term evaluates `lhs` for its effects .ail.json ─┐ ├─ load + validate schema ├─ resolve names + assign hashes + ├─ desugar (AST → AST, Iter 16a) ├─ typecheck (HM, effect rows) ├─ lower to MIR (SSA-like, named SSA values) ├─ emit LLVM IR (.ll) - └─ clang -O2 *.ll -o binary + └─ clang -O2 *.ll -o binary (links libgc for @GC_malloc) ``` +The **desugar** pass (`ailang-core::desugar::desugar_module`) runs +before typecheck and codegen in every entry point of `ailang-check` +and `ailang-codegen`. It is a pure AST → AST rewriter — currently +only flattens nested constructor patterns (16a), but is the chosen +home for any future surface-smoothing rewrites that should not bloat +the core AST or the backends. **Critical invariant:** `CheckedModule.symbols` +in the `check` entry point continues to hash from the *original* +on-disk module, not the desugared one, so `ail diff` and `ail manifest` +report identities that match the canonical JSON the user is editing. + ## CLI ``` -ail check — loads, validates, typechecks -ail manifest — table: name :: type !effects [hash] -ail describe — detail of a definition -ail render — JSON → pretty-print -ail parse — pretty-print → JSON (for bootstrapping) -ail emit-ir — writes .ll -ail build — full pipeline → binary -ail run — build + execute (tempdir), passthrough exit code +ail check — loads, validates, typechecks +ail manifest — table: name :: type !effects [hash] +ail describe — detail of a definition (form-A body) +ail render — JSON-AST → form-A text (exact inverse of `parse`) +ail parse — form-A text → canonical JSON-AST +ail deps — list cross-module references +ail diff — content-addressed def-level diff +ail workspace — list all modules transitively reachable from entry + (`--json` for machine output; + `manifest --workspace` and `diff --workspace` + extend single-module subcommands to workspaces) +ail builtins — list built-in fns and effect ops +ail emit-ir — writes .ll +ail build — full pipeline → binary +ail run — build + execute (tempdir), passthrough exit code ``` ## Verification and correctness (across cycles) @@ -723,8 +751,15 @@ ail run — build + execute (tempdir), passthrough exit ## What is not (yet) supported -Snapshot of the boundary at the end of Iter 13. Items move out of this list -as iterations land; the JOURNAL records the exact iteration. +Snapshot of the boundary as of Iter 16a. Items move out of this list +as iterations land; the JOURNAL records the exact iteration. Recently +**lifted** gates that used to live here: cross-module ADTs (lifted in +Iter 14h via qualified `module.Type` / `module.Ctor` references in +both `(con ...)` and `(term-ctor ...)` / `(pat-ctor ...)` positions); +GC for ADT boxes, lambda envs, and closure pairs (Boehm conservative +collector wired up in Iter 14f, see Decision 9); nested constructor +sub-patterns inside `match` (lifted in Iter 16a via the desugar pass — +literal sub-patterns are still rejected, see below). - No effect handlers — only the built-in IO and Diverge ops. - No refinements / SMT escalation. @@ -737,13 +772,15 @@ as iterations land; the JOURNAL records the exact iteration. instantiation, deferred. - No higher-rank polymorphism. Passing a polymorphic fn to another polymorphic fn (`apply(id, 42)`) is not supported. -- No cross-module ADTs. ADTs are local to a module; ctor names must be - unique within their module but may collide across modules. +- No literal sub-patterns inside a Ctor pattern. `(pat-ctor Cons 0 _)` + is rejected (`nested-ctor-pattern-not-allowed` from `ailang-check`) + because the pattern-match backend has no switch-on-i64 yet. + Workaround: bind a var and test in the arm body. +- No local recursive `let`. `let f = ... in ...` only sees `f`'s + binding inside the body, not inside its own RHS — recursion needs + a top-level def. - No visibility rules in imports. Every top-level def of an imported module is reachable; there is no `pub` / `priv`. -- No GC. ADT boxes, lambda envs, and closure pairs all leak. Acceptable - for current example programs; required before any longer-running - program. What **is** supported (and used as the smoke test for the pipeline): @@ -751,10 +788,30 @@ What **is** supported (and used as the smoke test for the pipeline): - `if`, `let`, function calls, recursion. - Effects on function signatures, with `do op(args)` for direct effect ops (`io/print_int`, `io/print_bool`, `io/print_str`). -- **ADTs + flat pattern matching** (Iter 3). Sub-patterns of a Ctor - pattern are restricted to `Var` / `Wild`. +- **ADTs + pattern matching** (Iter 3, extended in Iter 16a). Sub-patterns + of a Ctor pattern may be `Var`, `Wild`, **or another `Ctor`** (the + desugar pass flattens nested Ctor patterns into a chain of let + match + before typecheck/codegen — see `ailang-core::desugar` and Pipeline + above). Literal sub-patterns (`(pat-ctor Cons 0 _)`) are still rejected + by `ailang-check`. - **Imports + qualified cross-module references** via dotted names - (Iter 5). + (Iter 5). Extends to **types and constructors** (Iter 14h): a foreign + module's ADT is referenced as `(con std_pair.Pair a b)`, its ctors as + `(term-ctor std_pair.Pair MkPair x y)` and `(pat-ctor MkPair x y)` + inside that scrutinee. Std-library demos (`examples/std_*_demo.ail.json`) + exercise this end-to-end. +- **AI-authoring text surface, form (A)** (Decision 6 / Iter 14b–14c, + exclusive in 15e). The `ailang-surface` crate parses `.ailx` form-A + text into a canonical `ailang-core::ast::Module` and prints any module + back as form-A text. `ail render` and `ail describe` use it as the + sole text projection; `ail parse` is the inverse direction. Round-trip + identity (text → AST → JSON → AST → text) is gated by + `ailang-surface/tests/round_trip.rs` over every shipped fixture. +- **Memory management via Boehm conservative GC** (Decision 9 / Iter 14f). + Every ADT box, lambda env, and closure pair is allocated by `@GC_malloc`, + declared in the LLVM module preamble and linked from `libgc` at build + time. Soak-tested by `examples/gc_stress.ail.json` and the + `examples/std_list_stress.ail.json` fixture. - **First-class function references** (Iter 7). A top-level fn name (or qualified `prefix.def`) used as a `Term::Var` is a fn-value. - **Anonymous lambdas with capture** (Iter 8). `Term::Lam` constructs a @@ -802,3 +859,18 @@ Pipeline regression smoke tests: - `examples/maybe_int.ail.json` → prints 7 then 99 (pattern match over `Maybe`: `or_else(Some(7), 99)` then `or_else(None, 99)`). +- `examples/std_list_demo.ail.json` (Iter 15a/15b) → exercises + `std_list`'s combinators (length, sum, reverse, take/drop-style + uses) end-to-end against `std_list`'s `List`. +- `examples/std_maybe_demo.ail.json` (Iter 15c) → exercises `std_maybe` + combinators over `Maybe`, including `from_maybe` and `map`. +- `examples/std_either_demo.ail.json` (Iter 15d) → first program with + three distinct type variables in a single fn (the `either` + eliminator), monomorphised six different ways in the IR. +- `examples/std_pair_demo.ail.json` (Iter 15f) → drives every + `std_pair` combinator (fst, snd, swap, map_first, map_second); + expected output 7, 9, 9, 7, 8, 18. +- `examples/nested_pat.ail.json` (Iter 16a) → first program to use a + nested `(pat-ctor Cons a (pat-ctor Cons b _))`; the desugar pass + flattens it into a chain that the existing flat-match codegen + consumes. Prints 30 for a 3-element input list. diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index be97e8c..1fac678 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -2815,3 +2815,77 @@ the demo's `seq` chain, surfaced immediately by the parser's **Queue update.** 15f done. Remaining: 16b (local recursive `let`), 16c (Lit-in-Ctor patterns), 17a (per-fn arena). None blocking further stdlib growth; each is a quality-of-life improvement. + +--- + +## Iter 16a-aux — DESIGN.md drift audit (post-15f) + +**Goal.** After six feature iters (14g, 14h, 15a–15f, 16a) without a +docs sweep, the design doc had accumulated visible drift. Patch in +place rather than queueing the next codegen-heavy iter against a +stale spec — the user is unreachable for the broader memory-management +discussion that gates 17a, so this is the lowest-risk productive +move. + +**Drift sites found and fixed (DESIGN.md +72 LOC).** + +1. **Decision 6 status (L116).** Was: `(Iter 14b — WIP) ... Status: design pass in progress`. + Now: marks form (A) as shipped in Iter 14c, gated by the + round-trip test in `ailang-surface/tests/round_trip.rs`, and + notes that 15e made it the *sole* text projection (legacy + pretty-printer module helpers deleted). The body of the + section (constraints, candidate notations) remains as the + audit trail of the original design pass. +2. **Pipeline section (L687–696).** Inserted the **desugar pass** + between resolve+hash and typecheck, with the load-bearing + invariant explicit: `CheckedModule.symbols` hashes from the + *original* module, not the desugared one, so `ail diff` / + `ail manifest` keep reporting the on-disk identity. Also + noted libgc linkage on the clang line. +3. **CLI section (L700–708).** Added the four subcommands that + shipped in earlier iters but never made the doc: `deps`, + `diff` (single-module + `--workspace`), `workspace`, and + `builtins`. Reformatted to a two-column layout. +4. **"What is not (yet) supported" (L724–746).** Re-anchored + from "end of Iter 13" to "as of Iter 16a". Removed three + gates that had been lifted: cross-module ADTs (14h), no-GC + (14f, Decision 9), flat-pattern-only (16a). Replaced with + tighter follow-up gates: literal sub-patterns inside Ctor + patterns; local recursive `let`. Added a *one-paragraph* + "recently lifted" preamble so a future reader sees both the + delta and the iter that produced it without consulting JOURNAL. +5. **"What IS supported" (L782+).** Promoted four capabilities + into the smoke-test list: nested Ctor patterns via desugar + (16a); cross-module ADTs (14h); the form-(A) text surface as + shipped (14b/14c/15e); Boehm GC (Decision 9 / 14f). Replaced + "ADTs + flat pattern matching" line with one that names both + the original 3 and the 16a extension. +6. **Pipeline regression smoke tests (L819–).** Added the four + stdlib-demo fixtures (`std_list_demo`, `std_maybe_demo`, + `std_either_demo`, `std_pair_demo`) plus `nested_pat`. The + pre-existing entries (sum/list/hof/closure/list_map/sort/ + poly_id/poly_apply/box/maybe_int) were preserved as-is. + +**No code changes; tests still 93/93.** Verified with `cargo +build --workspace --quiet` — the doc-only edits do not affect +any compilation unit. + +**What was *not* changed.** The Goal section, Decisions 1–5, +Decisions 7–9 (already accurate after their respective revert / +ship statuses), Mangling scheme, Convention for cross-module +references, Data model (Module/Def/Term/Type grammar), Verification +section. Spot-checked each — all match current implementation. + +**Cumulative state, post-16a-aux.** + +- Stdlib: unchanged (4 modules, 24 combinators). +- DESIGN.md: 804 → 876 LOC. JOURNAL.md grows by this entry. +- Drift baseline reset: any future iter that lifts a gate or + ships a new pipeline layer should patch the relevant section + in the same iter, not accumulate. + +**Queue update.** 16a-aux done. Unchanged from post-15f: +16b (local recursive `let`), 16c (Lit-in-Ctor patterns), +17a (per-fn arena). 17a remains the explicit checkpoint before +any broader memory-management work — that decision is gated on +a joint conversation with the user.