lang: slice-of-protocol variadic ..xs: []P erases each arg to the protocol

packVariadicCallArgs stored the raw concrete arg into a [N x P] array when the
element type was a protocol, so an 8-byte struct landed in a 16-byte {ctx,
vtable} slot -> garbage vtable -> Bus error on dispatch. Now, when the slice
element type is a protocol, each arg is xx-erased to the protocol value via
buildProtocolErasure (same impl-driven machinery as the xx cast). This makes
..xs: []P the runtime, protocol-erased counterpart to the comptime
heterogeneous pack ..xs: P (which stays comptime-only): xs[runtime_i].method()
now works in an ordinary loop.

specs.md: full variadic/pack form-comparison table (concrete-vs-erased,
comptime-vs-runtime). Regression: examples/202. Issue 0052 (FIXED). 237 green.
This commit is contained in:
agra
2026-05-30 01:50:29 +03:00
parent 82bdcd634a
commit ab572359ae
6 changed files with 149 additions and 10 deletions

View File

@@ -963,6 +963,11 @@ path_join :: (..parts: []string) -> string { ... }
trailing args into a stack-allocated `[N x T]` and passes a slice over it.
- For `[]Any`, each trailing arg is boxed into `Any` (type tag + payload) before
packing; `args[i]` reads back the boxed value.
- For `[]Protocol` (the element type is a protocol, e.g. `..xs: []Show`), each
trailing arg is `xx`-erased to a protocol value `{ctx, vtable}` (impl-driven,
like `xx`) and packed into a runtime `[N]Protocol`. `xs[runtime_i].method()`
then dispatches through the protocol — this is the **runtime** counterpart to
the comptime heterogeneous pack `..xs: Protocol`.
- A `..` spread at the call site unpacks an existing slice/array into the variadic
tail: `sum(..arr)`.
- The heterogeneous comptime-pack form `..$args: []Type` binds per-position
@@ -972,18 +977,31 @@ path_join :: (..parts: []string) -> string { ... }
A **pack** is a comptime sequence of per-position-typed arguments. Unlike a
slice variadic (`..xs: []T`, one uniform element type, a runtime slice), a pack
binds a *distinct* type to each position and exists only at compile time. Three
declaration forms exist:
binds a *distinct* type to each position and exists only at compile time.
| Form | Element typing | Body view |
|---|---|---|
| `..xs: []T` | uniform `T` | runtime `[]T` slice |
| `..$xs: []Type` | per-position comptime *types* | comptime type list |
| `..xs: Protocol` | per-position — each arg conforms to `Protocol` with its own type-arg | per-position-typed pack |
The full family of variadic/pack forms and how they differ:
The third form is the heterogeneous pack. `map :: (mapper: ..., ..sources:
ValueListenable) -> ...` accepts any number of trailing args, each some
`ValueListenable(T)` for a possibly-different `T`.
| Form | Element types | Lives at | `xs[i]` index | `xs[i]` yields | `xs.len` |
|---|---|---|---|---|---|
| `..xs: []T` | one uniform `T` | **runtime** (slice) | runtime or comptime | `T` | runtime |
| `..xs: []Any` | mixed, **boxed** to `Any` | **runtime** (slice) | runtime or comptime | `Any` (match/unwrap to use) | runtime |
| `..xs: []P` *(P a protocol)* | mixed, **erased** to `P` `{ctx,vtable}` | **runtime** (slice) | runtime or comptime | `P` (call protocol methods) | runtime |
| `..xs: P` *(pack)* | per-position **concrete**, each conforms to `P` | **comptime** (no runtime value) | comptime only (literal / `inline for` cursor) | the concrete element, **viewed through `P`** | comptime int |
| `..$args` / `..$xs: []Type` | per-position comptime **types** | **comptime** | comptime only | element value/type (reflection) | comptime int |
Key axis — **concrete vs erased, comptime vs runtime**:
- `..xs: P` (pack) keeps each element's *concrete* type but is **comptime-only**:
`xs[i]` needs a compile-time index (a literal or an `inline for` cursor); a
runtime index is an error (a pack has no runtime representation). Use it when
you need per-position types (monomorphization, `xs.T` / `xs.value` projection).
- `..xs: []P` (slice of protocol) **erases** each element to the protocol value
but is **runtime**: `xs[runtime_i].method()` works in an ordinary loop. Use it
when you need to iterate the args at runtime and only the protocol interface
matters. It is the runtime counterpart to the pack.
The heterogeneous pack (`..xs: P`) is what powers `map :: (mapper: ...,
..sources: ValueListenable) -> ...`: it accepts any number of trailing args,
each some `ValueListenable(T)` for a possibly-different `T`.
A pack is **not a runtime value** — it lowers to N typed positional parameters
(zero overhead). The body refers to elements only through the comptime forms