Iter 16a: nested constructor patterns via AST desugaring
Lifts the gate that rejected `(pat-ctor Cons a (pat-ctor Cons b _))`.
Approach: a pure AST → AST pass `ailang_core::desugar::desugar_module`
that flattens nested-Ctor sub-patterns into chains of single-level
matches with let-bound fresh vars and duplicate fall-through. Both
ailang-check and ailang-codegen call the pass at pipeline entry.
Hash-relevant canonical bytes are untouched: the pass runs after
load_module, in memory only. `check`'s returned CheckedModule.symbols
still carries hashes of the original defs so `ail diff` / `ail manifest`
see on-disk identities.
Lit sub-patterns inside a Ctor still rejected — that's the narrower
scope of `nested-ctor-pattern-not-allowed` post-16a; the variant doc
reflects the new meaning.
Fresh-name safety: `$` is a valid ident character in form (A), so
the Desugarer pre-walks every Term::Var / Pattern::Var name into a
BTreeSet and bumps its counter past any user-spelled `$mp_N`.
Already-flat matches go through `is_flat()` and emit identical AST
shapes; every existing fixture produces the same output as before.
New: examples/nested_pat.{ailx,ail.json} prints 30 from
first_two_sum on [10, 20, 30]. e2e test
`nested_ctor_pattern_first_two_sum` guards the path.
Tests: 92/92 (e2e 32→33, ailang-core unit 10→12).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2670,3 +2670,101 @@ removed fn. Net same.)
|
||||
**Cumulative state, post-15e.** No new language features. Two
|
||||
canonical text projections collapsed to one (form A). Internal
|
||||
cleanup; no behavioural change for any program in the repo.
|
||||
|
||||
---
|
||||
|
||||
## Iter 16a — nested constructor patterns in `match`
|
||||
|
||||
**Goal.** Lift the gate that rejected `(pat-ctor Cons a (pat-ctor Cons b _))`
|
||||
and similar nested-Ctor sub-patterns. Lit-in-Ctor stays rejected;
|
||||
that's a separate iter.
|
||||
|
||||
**Approach: AST-level desugar before check + codegen.** Pure rewrite
|
||||
that flattens nested Ctor patterns into chains of single-level
|
||||
matches with let-bound fresh vars and duplicate fall-through.
|
||||
Hash-relevant canonical bytes untouched because the pass runs
|
||||
*after* `load_module`, in memory only. The checker / codegen always
|
||||
see flat patterns.
|
||||
|
||||
**Algorithm sketch.**
|
||||
|
||||
For a `Match` whose arms contain nested-Ctor sub-patterns:
|
||||
1. Bottom-up: recursively desugar scrutinee and arm bodies first.
|
||||
2. Let-bind the scrutinee to `$mp_N` (single eval).
|
||||
3. Build a chain `arm_1 (else arm_2 (else ... default))`. Default
|
||||
is `Lit Unit`, unreachable for valid programs.
|
||||
4. Each arm's nested Ctor sub-patterns are lifted to fresh vars,
|
||||
then deepest-first wrapped via `wrap_sub`, which on a Ctor sub
|
||||
recursively re-enters `desugar_match` — that recursion handles
|
||||
arbitrary depth.
|
||||
|
||||
`fall_k` is cloned per inner level → O(arms × depth) terms in
|
||||
worst case. Acceptable for typical patterns.
|
||||
|
||||
**Fresh-name safety.** `$` is a valid identifier character in form
|
||||
(A) (the lexer's `Ident` token is "anything not paren/int/string"),
|
||||
so `$mp_0` is theoretically user-writable. The Desugarer pre-walks
|
||||
every `Term::Var` and `Pattern::Var` name in the module into a
|
||||
`BTreeSet<String>` and bumps the counter past collisions.
|
||||
|
||||
**Files.**
|
||||
|
||||
- New: `crates/ailang-core/src/desugar.rs` (571 LOC including
|
||||
doc-comments and two unit tests). Public surface is
|
||||
`pub fn desugar_module(m: &Module) -> Module` — pure, idempotent,
|
||||
no I/O.
|
||||
- `crates/ailang-core/src/lib.rs` — `pub mod desugar;` plus a
|
||||
module-doc bullet describing the new pipeline layer.
|
||||
- `crates/ailang-check/src/lib.rs` — three public entries
|
||||
(`check_module`, `check_workspace`, `check`) call
|
||||
`desugar_module` first. The gate at the old line 1538 is now
|
||||
narrowed: nested **Ctor** sub-patterns are `unreachable!()`
|
||||
(desugared away); nested **Lit** still emits the existing
|
||||
`nested-ctor-pattern-not-allowed` diagnostic. Crucially:
|
||||
`check`'s returned `CheckedModule.symbols` keeps hashes of
|
||||
the *original* defs so `ail diff` and `ail manifest` see the
|
||||
on-disk identity, not a post-desugar one.
|
||||
- `crates/ailang-codegen/src/lib.rs` — `lower_workspace` desugars
|
||||
every module up front; `emit_ir` (single-file shortcut) goes
|
||||
through `lower_workspace` so the desugar runs there too.
|
||||
- New: `examples/nested_pat.ailx` + `nested_pat.ail.json`.
|
||||
`first_two_sum` matches `(pat-ctor Cons a (pat-ctor Cons b _))`;
|
||||
prints `30` for the input `[10, 20, 30]`.
|
||||
- `crates/ail/tests/e2e.rs` — new
|
||||
`nested_ctor_pattern_first_two_sum` test.
|
||||
|
||||
**Behavioural property of the desugar.** Already-flat matches go
|
||||
through `is_flat()` and emit identical AST shapes. Empirically:
|
||||
every existing fixture (std_maybe, std_list, std_either, list_map,
|
||||
sort, etc.) produces the same observable output as before because
|
||||
their patterns were already flat — the desugar is a no-op clone
|
||||
on them.
|
||||
|
||||
**Tests: 92/92.**
|
||||
- e2e: 33 (was 32, +1 for nested_pat).
|
||||
- ailang-core unit: 12 (was 10, +2 for the desugar tests).
|
||||
- All other crates unchanged.
|
||||
|
||||
**No new compiler bug surfaced.** The transform was straightforward
|
||||
because the AST already supported nested sub-patterns at the type
|
||||
level — only the checker gate and codegen drop-on-floor were
|
||||
artificial walls. Removing them via desugaring (rather than
|
||||
expanding the codegen) keeps the pattern-matching backend simple
|
||||
and lets future iters (Lit-in-pattern, exhaustiveness, decision
|
||||
trees) plug in at the same desugar layer.
|
||||
|
||||
**Cumulative state, post-16a.**
|
||||
|
||||
- Stdlib: 3 modules, 19 combinators (unchanged from 15d).
|
||||
- Language: nested Ctor patterns now legal; nested Lit-in-pattern
|
||||
still rejected (intentional follow-up scope).
|
||||
- Pipeline: `load → desugar → check → codegen`. The desugar layer
|
||||
is the natural home for further surface-level smoothing
|
||||
(Lit-in-pattern, `if`-as-syntactic-sugar, etc.) without
|
||||
enlarging the core AST.
|
||||
- Compiler bugs surfaced and fixed in dogfood since 14a: still 4
|
||||
(no fresh ones in 16a — the desugar landed clean).
|
||||
|
||||
**Queue update.** 16a done. Remaining: 15f (`std_pair`, optional);
|
||||
16b (local recursive `let`); 17a (per-fn arena); future
|
||||
"lit-in-Ctor" follow-up under 16c if and when needed.
|
||||
|
||||
Reference in New Issue
Block a user