w6c+wcc: widen struct/tagged-subset, parse ... spread

Three tagged-union gaps:

  1. Struct-payload widening was broken at every site (call, let,
     assign, return, struct-field init). cg_widen_tagged_store now
     materialises str / scalar / struct-lit / struct-ident / tagged
     payloads at slot+8+field_off and writes the tag last. Call sites
     route through cg_widen_tagged_push (scratch slot + push high→low).

  2. Tagged → wider tagged widening forwarded the source tag verbatim.
     cg_widen_tag_remap emits a CMPQ-chain switch that translates each
     source variant index to the destination's, then zero-pads to the
     wider slot. type_eq grew a TY_TAGGED arm (was returning 1 for any
     two unions); type_assignable now accepts variant-subset and
     rejects the rest.

  3. `(...inner | T)` spread parses (cmd/wcc/parse.c, lib/ww/parse).
     Marks Node.op = TK_ELLIPSIS; resolve_type unwraps NAMED + flattens
     when the spread bit is set so aliases inline like Hare's
     tagged_type unwrap flag.

Selfhost mirror: spread parser ported. Cgen widen helpers not yet
mirrored — wwstage stays byte-identical to cstage on the existing
test corpus, but will emit wrong asm if user code uses the new
patterns (probe sp2 shows the divergence).

700_e2e: 9 new rows covering call/let/assign/return × struct +
tagged subset, plus the spread-flatten case.
This commit is contained in:
2026-05-13 05:30:20 +09:00
parent 47d75d9b59
commit 9133251269
8 changed files with 562 additions and 161 deletions

View File

@@ -3952,14 +3952,24 @@ fn parsetype(p: *parser) *node = {
if (p.curkind == tkind.TK_LPAREN) {
// (T) or (T, T, ...) or (T | T | ...)
//
// Each tagged variant may be prefixed with `...` to mark a
// spread — when the variant resolves to another tagged union
// its variants are flattened into the enclosing union. We
// tag the spread on node.op = TK_ELLIPSIS so resolve_type
// can distinguish intent. Mirrors C parsetype.
advance(p);
let firstspread: bool = accepttok(p, tkind.TK_ELLIPSIS);
let first: *node = parsetype(p);
if (firstspread) { first.op = tkind.TK_ELLIPSIS; };
if (accepttok(p, tkind.TK_PIPE)) {
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
let head: *node = first;
let tail: *node = first;
for (true) {
let spread: bool = accepttok(p, tkind.TK_ELLIPSIS);
let e: *node = parsetype(p);
if (spread) { e.op = tkind.TK_ELLIPSIS; };
tail.next = e;
tail = e;
if (!accepttok(p, tkind.TK_PIPE)) { break; };
@@ -3968,6 +3978,9 @@ fn parsetype(p: *parser) *node = {
n.list = head;
return n;
};
if (firstspread) {
errmsg(p, "spread '...' only valid before tagged-union variants");
};
if (!accepttok(p, tkind.TK_COMMA)) {
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
return first;