ffi M5.A.next: checkpoint — pack feature step 1 done (1c.A → 1d.B)

Logs the four commits that closed out step 1 of the variadic
heterogeneous type packs feature:

- 1c.A: parser-rejection lock-in for `..$args` inside `Closure(...)`.
- 1c.B: parser + AST + types.zig `pack_start` representation.
- 1d.A: impl-matching concrete-only miss lock-in.
- 1d.B: pack-aware impl matching with $args + $R binding through
  `param_impl_pack_map` and `pack_bindings`.

Next step is plan step 2 — runtime `args[$i]` indexing + per-mono
mangling — opening the door to body-side pack reflection that
step 5 needs to retire the hand-rolled `Into(Block)` impls.

Also catches up the 1b entry which the prior session left
uncommitted in the working tree.
This commit is contained in:
agra
2026-05-27 12:58:57 +03:00
parent 08feb6040b
commit 35ef32ffdb

View File

@@ -6,8 +6,148 @@ add a test and make it pass — that's two commits).
## Last completed step
**M5.A.next.1d.B — variadic heterogeneous type packs: pack-aware impl
matching** (commit `08feb60`).
Pack-shaped impls (`impl P(...) for Closure(..$args) -> $R`) now
match concrete closure sources at xx resolution time. Concrete
impls retain priority — pack matching only fires when
`param_impl_map` misses on the concrete key.
New plumbing in [src/ir/lower.zig](../src/ir/lower.zig):
- `PackParamImplEntry` carries the pack-shaped source TypeId plus
the pack-var and ret-var names extracted from the impl AST's
`target_type_expr`. `registerParamImpl` detects pack-shaped
sources via `pack_start != null` on the resolved closure type
and additionally registers in a new `param_impl_pack_map`
keyed by `"Proto\x00<arg_mangled>"` (no source suffix).
- `tryUserConversion` re-shapes the lookup so the pack path runs
on miss. `tryPackImplMatch` walks the pack entries, verifies
the source's fixed prefix matches the impl's prefix, binds
the pack-var to the source's tail param TypeIds, binds the
ret-var (when the impl's return is generic) to the source
return, and monomorphises the convert method. Mangled name
stays keyed on the concrete source so distinct call shapes
monomorphise separately.
- `pack_bindings: ?StringHashMap([]const TypeId)` is saved /
restored around monomorphisation, mirroring `type_bindings`.
- `resolveClosureTypeWithBindings` handles the `closure_type_expr`
node during type resolution: when the closure carries a
`pack_name` AND `pack_bindings` has a binding for it, the
bound TypeIds are appended after the fixed prefix and the
result is a concrete (non-pack) closure type — so the impl
body's `self: Closure(..$args) -> $R` substitutes to the
concrete source closure during monomorphisation.
`examples/155-pack-impl-match.sx` flips from the
"no Into(Block) for cl_s32_bool__bool" lock-in diagnostic to
"pack impl match ok": one user-declared
`impl Into(Block) for Closure(..$args) -> $R` covers a
`Closure(s32, bool) -> bool` source that stdlib has no
hand-rolled impl for. The constructed Block isn't invoked
(invoke=null) — the test exercises matching + monomorphisation,
not the trampoline (step 5 of the plan).
Same-file duplicate pack impls diagnose at registration;
cross-module pack-impl visibility and multi-pack-impl
specificity are deferred (matching the concrete path's existing
TODOs).
193/193 example tests + `zig build test` green. Step 1 of the
pack-feature plan ("Parser + type rep + impl matching") is now
done.
**Next step** — Step 2 of the plan: runtime indexing
(`args[$i]`) lowers to positional access; per-mono mangling
extends with a stable pack-shape hash. Builder fns receive
`$args` (a comptime `[]Type`) as a regular value parameter.
Replaces a hand-rolled Into impl in stdlib once step 2 + step
3 (type-reflection intrinsics) land.
---
**M5.A.next.1d.A — pack impl matching: lock in concrete-only miss**
(commit `ce3c2fe`).
Pinned today's matching behaviour ahead of 1d.B. A user-declared
`impl Into(Block) for Closure(..$args) -> $R` registers under a
pack-shaped source key in `param_impl_map`; the xx site mangles
the concrete `Closure(s32, bool) -> bool` source and finds
nothing → the existing focused diagnostic fires ("no `Into(Block)
for cl_s32_bool__bool` impl — add a per-signature
`__block_invoke_<sig>` trampoline + Into impl..."). The pack impl
is reachable in the file but never considered.
`examples/155-pack-impl-match.sx` captures the rejection at line
43 column 21 (the `xx cl : *Block` site). 193/193 example tests
+ `zig build test` green.
---
**M5.A.next.1c.B — pack type rep: Closure(..$args) parses + interns**
(commit `6582449`).
`parseTypeExpr`'s `Closure(...)` arm now accepts a trailing
`..$name` (sigil optional) as a variadic-pack marker. Pack must
be terminal — `)` is the only token accepted after the name.
`ClosureTypeExpr` AST gains `pack_name: ?[]const u8` carrying
the identifier so later slices can name the binding.
`FunctionInfo` / `ClosureInfo` in `src/ir/types.zig` grow a
`pack_start: ?u32 = null` field. `Closure(..$args) -> R` interns
as `params = []`, `pack_start = Some(0)` — distinct from any
concrete `Closure(...) -> R` shape thanks to updated hash/eql
arms. New constructor pair `closureTypePack` /
`functionTypePack` keeps the existing single-shape constructors
unchanged.
`type_bridge.resolveClosureType` calls `closureTypePack` when
`pack_name != null`. The pack starts after the fixed prefix,
so `Closure(Prefix, ..$args)` resolves with `params = [Prefix]`,
`pack_start = Some(1)`.
`examples/154-pack-type-rep.sx` flips from rejecting-with-error
to positive parse smoke. 192/192 example tests + `zig build
test` green.
---
**M5.A.next.1c.A — pack type rep: lock in parser rejection**
(commit `bb6eca6`).
Locked in today's `parseTypeExpr` Closure-arm rejection of `..$args`.
`examples/154-pack-type-rep.sx` uses `..$args` inside a
`Closure(...)` type expression — the pack-shape spelling used by
impl headers like `impl Into(Block) for Closure(..$args) -> $R`.
Today's parser recognized `..$args` only at parameter-list sites
(1b); the Closure type arm called `parseTypeExpr` per position
and hit "expected type name" at line 18 column 26.
---
**M5.A.next.1b — variadic heterogeneous type packs: parser accepts
`..$args`** (commit `a51fe26`).
`parseParams()` in `src/parser.zig:1558` accepts a leading `..`
before the optional `$` sigil and the parameter name. The old
`args: ..T` form (variadic marker after the colon) still works —
both paths set the same `is_variadic` flag. A pack declaration
`..$args` parses as:
- `is_variadic = true` (leading `..`)
- `is_comptime = true` (the `$` sigil)
- `type_expr = inferred_type` (no `:` annotation)
`examples/150-pack-parse.sx` flipped from rejecting-with-error to
positive parse smoke. The no-colon branch of `parseParams`
propagates `is_variadic` and `is_comptime` onto the Param
struct, so later slices can read both flags from the parsed AST.
191/191 example tests + `zig build test` green.
---
**M5.A.next.1a — variadic heterogeneous type packs: parse lockin**
(this commit).
(`ad82847`).
First slice of the `..$args` (variadic heterogeneous type pack)
feature per the plan saved at