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:
@@ -323,9 +323,16 @@ resolve_type(Checker *c, Node *n)
|
||||
for (Node *e = n->list; e; e = e->next) {
|
||||
Type *vt = resolve_type(c, e);
|
||||
if (vt == ty_never) continue;
|
||||
if (vt && vt->kind == TY_TAGGED) {
|
||||
/* flatten anonymous nested tagged */
|
||||
for (Tparam *src = vt->params; src; src = src->next) {
|
||||
int spread = (e->op == TK_ELLIPSIS);
|
||||
/* `...inner` spread: flatten the variants of the
|
||||
* (possibly NAMED) inner tagged union into the
|
||||
* enclosing union — matches Hare's parse-time
|
||||
* unwrap flag on each tagged_type entry. */
|
||||
Type *vu = spread && vt && vt->kind == TY_NAMED
|
||||
? vt->under : vt;
|
||||
if (vu && vu->kind == TY_TAGGED &&
|
||||
(spread || vt->kind == TY_TAGGED)) {
|
||||
for (Tparam *src = vu->params; src; src = src->next) {
|
||||
Type *st = src->type;
|
||||
if (st == ty_never) continue;
|
||||
if (variant_present(head, st)) continue;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user