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

@@ -292,14 +292,27 @@ parsetype(Parser *p)
/* Three forms inside the parens:
* (T) — parenthesised single type
* (T, T2, ...) — tuple type
* (T | T2 | ...) — tagged-union type (Hare-style sum) */
* (T | T2 | ...) — tagged-union type (Hare-style sum)
*
* 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 (today the checker
* flattens any nested tagged unconditionally, matching
* Hare's structural-equivalence rule, but the marker is
* preserved for future nominal handling). */
advance(p);
int first_spread = accept(p, TK_ELLIPSIS);
Node *first = parsetype(p);
if (first_spread) first->op = TK_ELLIPSIS;
if (accept(p, TK_PIPE)) {
Node *t = newnode(p->a, N_TTAGGED, pp);
Node *head = first, *tail = first;
for (;;) {
int spread = accept(p, TK_ELLIPSIS);
Node *e = parsetype(p);
if (spread) e->op = TK_ELLIPSIS;
tail->next = e;
tail = e;
if (!accept(p, TK_PIPE)) break;
@@ -308,6 +321,10 @@ parsetype(Parser *p)
t->list = head;
return t;
}
if (first_spread) {
errorf(pp, "spread '...' only valid before tagged-union variants");
p->errs++;
}
if (!accept(p, TK_COMMA)) {
expect(p, TK_RPAREN);
return first;