w6c+selfhost+lib: Hare-style variadic call sites

Param-decl `name: T...` (Tparam.variadic=1, type []T), call-site
gather of N args into a fresh `[N]T`, forward via `xs...`, full
selfhost mirror, and lib/fmt graduated to the Hare shape.

Frontend:
  - parse: `T...` after a param's type stamps Node.op=TK_ELLIPSIS
    and breaks out (variadic must be last).
  - check: resolve_type N_TFN / build_fn_type wrap the param type
    as []T and set tp->variadic. N_CALL accepts either a tail of
    args assignable to T (gather) or a single `xs...` spread of
    []T (forward); both bypass the "too many args" check on the
    variadic slot.
  - type: type_eq compares Tparam.variadic.

Cgen (cstage):
  - call site: when the callee has a variadic last param,
    materialise the tail args into a frame-resident `[N]T` via
    localoff, write a 24B slice descriptor (ptr,len,cap), and
    splice a synthesised N_IDENT into args[] so the downstream
    widen/eval/pop loops see one slice slot. Tagged-element types
    route each store through cg_widen_tagged_store. Forwarding
    skips gather: the N_SPREAD wrapper is replaced with its inner
    slice expression. Empty form writes {nil,0,0}. args[] / widen[]
    bump from 16 to 64 to accommodate Hare's mixed-arg printers.

Selfhost mirror:
  - lib/ww/parse: `T...` mark on N_PARAM.op.
  - cgen: varargseq counter on Cg; scanlocals reserves
    @vararg_d_N + @vararg_sl_N per variadic call (seq recorded on
    N_CALL.uval so cgcall picks the same names). cgcall does the
    same gather/forward and N_IDENT splice. cgfnparams treats
    variadic params as 24B slice slots via a synthesised TSLICE
    tnode. pushargsrev skips the tagged-widen detection for
    variadic params (effective type is []T, not tagged).
  - rhstargetname now recognises N_TRUE/N_FALSE/N_RUNELIT and
    typed N_INTLIT so the variant-tag lookup finds bool/rune/iN
    variants instead of falling through to "first non-str" (which
    misassigned tag 0 to bool in tagged unions like formattable).

lib/fmt graduated: print/println/fprint/fprintln/errorln/fatal
take `args: formattable...`. Bare `error` (no -ln) is skipped —
the leaf name collides with strconv's `type error = !(invalid |
overflow)` under the driver's flat namespace.

Tests: 5 new e2e rows (plain gather, zero-arg, tagged element,
forwarding, fmt.println end-to-end). lib/CLAUDE.md workaround
paragraph replaced with the Hare-shape description.
This commit is contained in:
2026-05-13 08:56:01 +09:00
parent 46edb8db4a
commit b6cf68f2b8
15 changed files with 1327 additions and 140 deletions

View File

@@ -1558,6 +1558,75 @@ static const struct row rows[] = {
" };\n"
" return s;\n"
"};", 42 }, /* 1 + 2 + 39 = 42 */
/* Hare-style variadic gather: `args: T...` declares an N-arg
* variadic; the call-site materialises N values into a fresh
* `[N]T` and synthesises a {ptr,len,cap} slice for the param.
* Plain element type (i64): no widening, MOVQ-per-element. */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = {\n"
" return sum(1i64, 2i64, 3i64, 7i64, 9i64, 20i64): i32;\n"
"};", 42 },
/* Variadic with zero args: empty-slice descriptor {nil,0,0}.
* Confirms the gather path doesn't crash on N=0. */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn main() i32 = {\n"
" let a: i64 = sum();\n"
" let b: i64 = sum(42i64);\n"
" return (a + b): i32;\n"
"};", 42 },
/* Variadic with tagged-union element type: each gathered arg
* widens to the variant's slot shape (tag@+0, payload@+8). The
* runtime match-dispatch reads (i64=1)+(str.len=2)+(bool=39)=42. */
{ "type formattable = (i64 | str | bool);\n"
"fn sumtag(args: formattable...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) {\n"
" match (args[i]) {\n"
" case let n: i64 => s += n;\n"
" case let v: str => s += v.len: i64;\n"
" case let b: bool => { if (b) { s += 39i64; }; };\n"
" };\n"
" i += 1;\n"
" };\n"
" return s;\n"
"};\n"
"fn main() i32 = { return sumtag(1i64, \"hi\", true): i32; };", 42 },
/* Variadic forwarding: `wrap(args...)` passes the local slice
* directly to `sum`, no re-gather. Mirrors Hare's wrapper shape
* (`fn println(args: formattable...) = fprintln(os.stdout, args...)`). */
{ "fn sum(args: i64...) i64 = {\n"
" let s: i64 = 0i64;\n"
" let i: i32 = 0;\n"
" for (i < args.len) { s += args[i]; i += 1; };\n"
" return s;\n"
"};\n"
"fn wrap(prefix: i64, args: i64...) i64 = {\n"
" return prefix + sum(args...);\n"
"};\n"
"fn main() i32 = {\n"
" return wrap(2i64, 1i64, 2i64, 3i64, 4i64, 5i64, 7i64, 18i64): i32;\n"
"};", 42 },
/* lib/fmt user-side: `fmt.println(args: formattable...)` gathers
* mixed-type args at the call site. End-to-end exercises the
* lib/fmt graduation: the wrapper-chain `println → fprintln →
* fprint` is itself variadic-forwarding, so this validates both
* gather (at main) and `args...` forward (inside lib/fmt). The
* exit code is bytes printed (`hello 7\n` = 8). */
{ "use fmt;\n"
"fn main() i32 = {\n"
" return fmt.println(\"hello\", 7i64): i32;\n"
"};", 8 },
{ NULL, 0 }
};