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

@@ -243,6 +243,19 @@ type_eq(Type *a, Type *b)
}
return pa == NULL && pb == NULL;
}
case TY_TAGGED: {
/* Tagged unions are structurally equal iff variant lists
* match position-by-position. Nullable fold is a per-Type
* flag, so equal-up-to-fold types compare not-equal here —
* the caller can unwrap intentionally if needed. */
if (a->nullable != b->nullable) return 0;
Tparam *pa = a->params, *pb = b->params;
while (pa && pb) {
if (!type_eq(pa->type, pb->type)) return 0;
pa = pa->next; pb = pb->next;
}
return pa == NULL && pb == NULL;
}
default: return 1; /* primitives */
}
}
@@ -255,11 +268,14 @@ type_assignable(Type *dst, Type *src)
if (src == ty_never) return 1; /* bottom flows into anything */
if (type_eq(dst, src)) return 1;
/* Tagged-union variant inclusion: src is one of dst's variants.
* Checked before the untyped branch so untyped literals (e.g.
* 0, "msg") flow through to a variant's typed slot. Unwraps a
* named alias on either side so `type result = (T | E);` also
* accepts variants and the inverse. */
/* Tagged-union assignment:
* - concrete → tagged: src must match one of dst's variants.
* - tagged → tagged: src is assignable when every variant of
* src appears as a variant of dst (Hare-style subset). Tag
* remap at the use site handles different variant indices.
* Checked before the untyped branch so untyped literals flow
* through to a variant's typed slot. Unwraps a NAMED alias on
* either side so `type result = (T|E);` accepts variants too. */
{
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : src;
@@ -269,6 +285,18 @@ type_assignable(Type *dst, Type *src)
if (type_assignable(p->type, src)) return 1;
return 0;
}
if (du && du->kind == TY_TAGGED &&
su && su->kind == TY_TAGGED) {
for (Tparam *sp = su->params; sp; sp = sp->next) {
int ok = 0;
for (Tparam *dp = du->params; dp; dp = dp->next)
if (type_eq(dp->type, sp->type)) {
ok = 1; break;
}
if (!ok) return 0;
}
return 1;
}
}
/* Untyped → typed: only if the typed kind can hold the value. */