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:
487
cmd/w6c/cgen.c
487
cmd/w6c/cgen.c
@@ -771,6 +771,9 @@ localfind(Local *head, const char *name)
|
||||
|
||||
static void cgexpr(Cg*, Node*, Local*);
|
||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
|
||||
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int);
|
||||
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
||||
|
||||
static void
|
||||
cgexpr_int(Cg *c, long long v)
|
||||
@@ -778,6 +781,232 @@ cgexpr_int(Cg *c, long long v)
|
||||
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
|
||||
}
|
||||
|
||||
/* cg_widen_tag_remap — when widening from one tagged union to another,
|
||||
* rewrite the source's variant tag at BP+slot_off+0 to use the dst
|
||||
* union's variant indices. No-op when src and dst index orders coincide.
|
||||
*
|
||||
* Mirrors Hare's tagged-subset assignment: a value of type (A|B) flows
|
||||
* into (A|B|C) by re-tagging the discriminator to the position the
|
||||
* variant occupies in the wider union. Both must already match by
|
||||
* cg_variant_match — the checker enforces that.
|
||||
*
|
||||
* Emits a CMPQ-chain switch over the source tag because w6a has no
|
||||
* CMOVQ encoding. The chain is linear in nvariants; in practice tagged
|
||||
* unions are small. */
|
||||
static void
|
||||
cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
|
||||
{
|
||||
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||
if (su == NULL || su->kind != TY_TAGGED) return;
|
||||
int identity = 1, idx = 0;
|
||||
for (Tparam *p = su->params; p; p = p->next, idx++) {
|
||||
int di = cg_tag_for_variant(du, p->type);
|
||||
if (di < 0) di = 0;
|
||||
if (di != idx) { identity = 0; break; }
|
||||
}
|
||||
if (identity) return;
|
||||
const char *done = mklabel(c, "remap_done");
|
||||
ins2(c, A_MOVQ, amem(D_BP, slot_off + 0), areg(D_AX));
|
||||
idx = 0;
|
||||
for (Tparam *p = su->params; p; p = p->next, idx++) {
|
||||
const char *next = mklabel(c, "remap_next");
|
||||
int di = cg_tag_for_variant(du, p->type);
|
||||
if (di < 0) di = 0;
|
||||
ins2(c, A_CMPQ, aimm(idx), areg(D_AX));
|
||||
ins1(c, A_JNE, abranch(next));
|
||||
ins2(c, A_MOVQ, aimm(di), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||
ins1(c, A_JMP, abranch(done));
|
||||
label(c, next);
|
||||
}
|
||||
label(c, done);
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_store — write the tagged-union slot bytes for `src`
|
||||
* into BP+slot_off, sized to `sz` (8 for nullable fold, else 16/24+).
|
||||
* Used by call-site widening (via cg_widen_tagged_push) and by the
|
||||
* let/assign/return/struct-field-init paths.
|
||||
*
|
||||
* Branches by source shape (tagged_arg_size > 0 source counts as a
|
||||
* tagged subset — possibly with different variant indices):
|
||||
* - nullable: dst is folded (*T|void); store pointer at +0.
|
||||
* - tagged ident: byte-copy slot words then remap tag at +0.
|
||||
* - tagged expression: cgexpr leaves AX=tag, DX=val0, [CX=val1] —
|
||||
* spill into slot then remap.
|
||||
* - struct ident: zero-fill, byte-copy struct words to +8.
|
||||
* - struct literal: zero-fill, store each field at slot+8+field_off.
|
||||
* - str: cgexpr leaves AX=ptr, BX=len.
|
||||
* - scalar: cgexpr leaves AX; store at +8 with zero pad. */
|
||||
static void
|
||||
cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
int slot_off, int sz)
|
||||
{
|
||||
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||
if (du->nullable) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
Type *st = src ? src->type : NULL;
|
||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
/* Tagged → tagged subset: copy slot words then tag-remap. */
|
||||
if (su && su->kind == TY_TAGGED) {
|
||||
int ssz = (int)su->size;
|
||||
if (src->kind == N_IDENT) {
|
||||
int soff = localfind(*locals_p, src->str);
|
||||
for (int k = 0; k < ssz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
}
|
||||
} else {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 0));
|
||||
if (ssz > 8)
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, slot_off + 8));
|
||||
if (ssz > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, slot_off + 16));
|
||||
}
|
||||
if (ssz < sz) {
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = ssz; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
}
|
||||
cg_widen_tag_remap(c, du, su, slot_off);
|
||||
return;
|
||||
}
|
||||
/* Struct payload: zero the whole slot, then write fields/words
|
||||
* at slot+8+ — keeping the tag word at slot+0 from the zero-fill,
|
||||
* then patch it with the variant tag. */
|
||||
if (su && su->kind == TY_STRUCT) {
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + k));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
if (src->kind == N_IDENT) {
|
||||
int soff = localfind(*locals_p, src->str);
|
||||
int ssz = (int)su->size;
|
||||
int k = 0;
|
||||
while (k + 8 <= ssz) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + k));
|
||||
k += 8;
|
||||
}
|
||||
if (k < ssz) {
|
||||
/* Tail word: load with the right width to
|
||||
* avoid stepping past the source slot. The
|
||||
* zero-fill above means trailing slop is
|
||||
* already clean. */
|
||||
int tail = ssz - k;
|
||||
int lop = (tail == 4) ? A_MOVL :
|
||||
(tail == 1 ? A_MOVB : A_MOVQ);
|
||||
ins2(c, lop,
|
||||
amem(D_BP, soff + k), areg(D_AX));
|
||||
ins2(c, lop, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + k));
|
||||
}
|
||||
} else if (src->kind == N_STRUCTLIT) {
|
||||
for (Node *f = src->list; f; f = f->next) {
|
||||
u64 foff = 0;
|
||||
int fsz = 8;
|
||||
Type *ftype = NULL;
|
||||
for (Tfield *fl = su->fields; fl; fl = fl->next) {
|
||||
if (strcmp(fl->name, f->str) == 0) {
|
||||
foff = fl->offset;
|
||||
fsz = (int)(fl->type ? fl->type->size : 8);
|
||||
ftype = fl->type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
int sl_isf32 = 0;
|
||||
if (fld_isfloat(ftype, &sl_isf32)) {
|
||||
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BP, slot_off + 8 + (int)foff));
|
||||
continue;
|
||||
}
|
||||
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||
? ftype->under : ftype;
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff + 8));
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
if (fsz == 1) op = A_MOVB;
|
||||
else if (fsz == 4) op = A_MOVL;
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, slot_off + 8 + (int)foff));
|
||||
}
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* str payload: AX=ptr, BX=len from cgexpr. */
|
||||
if (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, slot_off + 0));
|
||||
return;
|
||||
}
|
||||
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
|
||||
* left untouched here — match dispatches on the tag word first
|
||||
* and only the str branch reads slot+16, so leaving the pad
|
||||
* uninitialised in let/assign matches the pre-refactor asm.
|
||||
* cg_widen_tagged_push pre-zeroes the scratch slot before
|
||||
* calling us, so the call-site push still sees clean pad. */
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, slot_off + 0));
|
||||
}
|
||||
|
||||
/* cg_widen_tagged_push — call-site widening. Materialise the tagged
|
||||
* value in a stack scratch slot then push slot words high→low so the
|
||||
* arg-register pop drain sees tag first, then payload words. */
|
||||
static void
|
||||
cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
{
|
||||
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du && du->nullable) {
|
||||
/* Single 8B slot: just push the pointer/null. */
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
return;
|
||||
}
|
||||
const char *scr_name = mklabel(c, "argscr");
|
||||
int scr = local_alloc(c, locals_p, scr_name, sz, cg_frame);
|
||||
/* Zero the scratch slot first so any pad word the store path
|
||||
* leaves untouched (scalar variant in a >16B slot, struct payload
|
||||
* shorter than the slot's value area) reads as 0 on the callee.
|
||||
* The store path then writes the variant bytes over the zeros. */
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, locals_p, dst, src, scr, sz);
|
||||
int nwords = sz / 8;
|
||||
for (int k = nwords - 1; k >= 0; k--) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + k * 8), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cgexpr(Cg *c, Node *n, Local *locals)
|
||||
{
|
||||
@@ -1570,9 +1799,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Plain `r = expr;` where r is a tagged-union local. Mirrors
|
||||
* the let-init path: forward AX:DX[:CX] when rhs is itself
|
||||
* tagged, else synthesise the tag from rhs's static type. */
|
||||
/* Plain `r = expr;` where r is a tagged-union local.
|
||||
* Delegates to cg_widen_tagged_store: covers nullable fold,
|
||||
* tagged→tagged (with tag remap), struct payload (ident or
|
||||
* literal), str payload, and scalar payload. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||
&& n->lhs->type) {
|
||||
Type *lt = n->lhs->type;
|
||||
@@ -1580,31 +1810,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (lu && lu->kind == TY_TAGGED) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) break;
|
||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
||||
if (type_istagged(rt)) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, off + 8));
|
||||
if (lu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
int tag = cg_tag_for_variant(lu, rt);
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (type_isstr(rt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + 0));
|
||||
}
|
||||
cg_widen_tagged_store(c, &locals, lu, n->rhs,
|
||||
off, (int)lu->size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2072,9 +2279,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
callee_t->under : callee_t;
|
||||
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
||||
cu->params : NULL;
|
||||
/* widen[i]: param is tagged, arg is a concrete variant.
|
||||
* widen_sz[i]: param's tagged slot size (8/16/24).
|
||||
* widen_param[i]: param type (for tag-index lookup). */
|
||||
/* widen[i]: param is tagged and arg needs re-layout.
|
||||
* - arg is a concrete variant (str/struct/scalar) — wrap
|
||||
* in the param's slot shape.
|
||||
* - arg is itself a tagged union of a subset/different
|
||||
* variant set — copy the slot words and remap the tag.
|
||||
* Identical types pass through unchanged. */
|
||||
int widen[16] = {0};
|
||||
int widen_sz[16] = {0};
|
||||
Type *widen_param[16] = {0};
|
||||
@@ -2084,18 +2294,27 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (p == NULL) break;
|
||||
Type *at = args[i] ? args[i]->type : NULL;
|
||||
int psz = tagged_arg_size(p->type);
|
||||
int arg_tagged = tagged_arg_size(at) > 0;
|
||||
if (psz > 0 && !arg_tagged) {
|
||||
widen[i] = 1;
|
||||
widen_sz[i] = psz;
|
||||
widen_param[i] = p->type;
|
||||
if (psz > 0) {
|
||||
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||
? p->type->under : p->type;
|
||||
Type *au = (at && at->kind == TY_NAMED)
|
||||
? at->under : at;
|
||||
int same = (pu == au) || type_eq(p->type, at);
|
||||
if (!same) {
|
||||
widen[i] = 1;
|
||||
widen_sz[i] = psz;
|
||||
widen_param[i] = p->type;
|
||||
}
|
||||
}
|
||||
p = p->next;
|
||||
}
|
||||
}
|
||||
/* eval right-to-left, push to stack */
|
||||
/* eval right-to-left, push to stack. Each N_IDENT fast-path
|
||||
* is guarded by !widen[i] so the tagged-union widening (which
|
||||
* needs to synthesise tag + payload + pad) takes precedence
|
||||
* over the verbatim slice/struct/tagged-ident loads below. */
|
||||
for (int i = argcount - 1; i >= 0; i--) {
|
||||
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||
int off = localfind(locals, args[i]->str);
|
||||
/* push cap, len, ptr (top) so pops give ptr,len,cap */
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
||||
@@ -2153,7 +2372,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */
|
||||
continue;
|
||||
}
|
||||
if (node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
/* load qword(s) directly from the struct's slot */
|
||||
int off = localfind(locals, args[i]->str);
|
||||
int sz = struct_arg_size(args[i]->type);
|
||||
@@ -2165,7 +2384,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
continue;
|
||||
}
|
||||
if (node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
if (!widen[i] && node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||
/* Tagged-union: push each 8B word from the slot.
|
||||
* High word goes first so the popper drains them
|
||||
* in low→high order into the arg-register class. */
|
||||
@@ -2182,41 +2401,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (widen[i]) {
|
||||
/* Concrete → tagged-union widening at the call
|
||||
* site. Mirrors the let/assign/return widening:
|
||||
* synthesise tag from the arg's static variant
|
||||
* type, evaluate the arg, lay it out as the
|
||||
* parameter's tagged slot, then push high→low so
|
||||
* pop drains tag first. */
|
||||
int tag = cg_tag_for_variant(widen_param[i],
|
||||
args[i]->type);
|
||||
if (tag < 0) tag = 0;
|
||||
int sz = widen_sz[i];
|
||||
if (sz == 8) {
|
||||
/* Nullable fold: the pointer value IS the
|
||||
* discriminator — no separate tag word. */
|
||||
cgexpr(c, args[i], locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, args[i], locals);
|
||||
if (node_isstr(args[i])) {
|
||||
/* slot 24: [+0]=tag,[+8]=ptr,[+16]=len */
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
} else {
|
||||
/* Scalar variant: single value word at +8.
|
||||
* Pad a zero high word when the slot is 24B
|
||||
* (some other variant of the union is 16B). */
|
||||
if (sz > 16) {
|
||||
ins2(c, A_XORQ, areg(D_DX),
|
||||
areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
* lay out the value in the parameter's slot
|
||||
* shape, then push high→low so pop drains tag
|
||||
* first.
|
||||
*
|
||||
* Branches by source shape:
|
||||
* - nullable (sz==8): pointer IS the disc.
|
||||
* - str: tag@+0, ptr@+8, len@+16.
|
||||
* - struct ident: copy struct words then
|
||||
* prepend tag, zero-pad to slot size.
|
||||
* - struct literal: materialise via a stack
|
||||
* scratch slot — store each field at its
|
||||
* struct-relative offset (with the +8 tag
|
||||
* shift), zero-fill, then push from slot.
|
||||
* - tagged source: load src slot words, remap
|
||||
* the tag word via cg_widen_tag_remap, pad
|
||||
* to wider dst slot, push.
|
||||
* - scalar: tag@+0, value@+8, optional pad. */
|
||||
cg_widen_tagged_push(c, &locals, widen_param[i],
|
||||
args[i], widen_sz[i]);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, args[i], locals);
|
||||
@@ -3327,47 +3530,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Tagged-union initialiser. Two shapes:
|
||||
* 1) rhs already produces a tagged-union value (e.g. a fn
|
||||
* call returning (T | E)). cgexpr leaves AX=tag,
|
||||
* DX=value0[, CX=value1] — copy each into the slot.
|
||||
* 2) rhs is a bare variant value (e.g. `let r: (i64|i32) = 7`).
|
||||
* Synthesise the tag from rhs's static type and store it
|
||||
* alongside the value. str-typed rhs flows as
|
||||
* (AX=ptr, BX=len) so we store both halves.
|
||||
*
|
||||
* Nullable folded `(*T | void)`: the slot is a single 8B
|
||||
* pointer. Both the value-from-call and bare-variant paths
|
||||
* simplify to "spill AX". void variant stores 0; *T variant
|
||||
* stores the pointer. */
|
||||
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
||||
* which handles nullable fold, tagged→tagged (with tag remap
|
||||
* when variant indices differ), struct payload (ident or
|
||||
* literal — field-by-field at slot+8+field_off), str payload,
|
||||
* and scalar payload (with zero-pad to the slot size). */
|
||||
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
||||
Type *rt = n->rhs->type;
|
||||
if (lu->nullable) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 0));
|
||||
} else if (type_istagged(rt)) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||
if (lu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
int tag = cg_tag_for_variant(lu, rt);
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
if (type_isstr(rt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + 0));
|
||||
}
|
||||
cg_widen_tagged_store(c, locals, lu, n->rhs, off, sz);
|
||||
break;
|
||||
}
|
||||
/* slice expression initialiser: build a {ptr, len, cap} header
|
||||
@@ -3472,37 +3641,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Tagged-union field: synthesise tag from
|
||||
* f->lhs's static type and store value bytes
|
||||
* (1 or 2 words for ≤8B/str variants). */
|
||||
/* Tagged-union field: delegate to the shared
|
||||
* widening writer. Handles str, scalar, struct
|
||||
* literal/ident payload, and tagged-subset
|
||||
* forwarding (with tag remap). The field's slot
|
||||
* starts at off+foff inside the struct slot. */
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
if (fu && fu->kind == TY_TAGGED) {
|
||||
Type *vt = f->lhs ? f->lhs->type : NULL;
|
||||
int tag = cg_tag_for_variant(fu, vt);
|
||||
cgexpr(c, f->lhs, *locals);
|
||||
if (type_isstr(vt)) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, off + (int)foff + 16));
|
||||
} else if (type_istagged(vt)) {
|
||||
/* forwarding a tagged value */
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
if (fu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, off + (int)foff + 16));
|
||||
/* AX already holds the tag */
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 0));
|
||||
continue;
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, off + (int)foff + 8));
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, off + (int)foff + 0));
|
||||
cg_widen_tagged_store(c, locals, fu,
|
||||
f->lhs, off + (int)foff,
|
||||
(int)fu->size);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals);
|
||||
@@ -3615,18 +3764,25 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||
if (rt && rt->kind == TY_TAGGED) {
|
||||
Type *vt = n->lhs->type;
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
Type *vu = (vt && vt->kind == TY_NAMED)
|
||||
? vt->under : vt;
|
||||
int istagged = vu && vu->kind == TY_TAGGED;
|
||||
int passthrough = istagged && (vu == rt ||
|
||||
type_eq(vt, cg_ret_type));
|
||||
int isstruct = vu && vu->kind == TY_STRUCT;
|
||||
if (rt->nullable) {
|
||||
/* AX already holds the pointer (or
|
||||
* 0 if the value was `nil` / `void`).
|
||||
* No tag word, no shuffle. */
|
||||
} else if (!type_istagged(vt)) {
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (passthrough) {
|
||||
/* same tagged type: forward AX/DX/CX. */
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
} else if (!istagged && !isstruct) {
|
||||
/* str / scalar variant: synthesise the
|
||||
* tag in AX and shuffle the value into
|
||||
* DX[/CX]. Direct register path keeps
|
||||
* the asm short — no scratch slot. */
|
||||
int tag = cg_tag_for_variant(rt, vt);
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
if (type_isstr(vt)) {
|
||||
/* AX=ptr, BX=len → DX=ptr,
|
||||
* CX=len, AX=tag. Move BX
|
||||
* before AX since the tag
|
||||
* MOVQ trashes AX. */
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -3637,6 +3793,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
/* Struct variant or tagged-subset:
|
||||
* materialise the widened value in a
|
||||
* scratch slot, then load AX/DX/CX
|
||||
* from the slot. Struct literal: field
|
||||
* stores; struct ident: word copy;
|
||||
* tagged subset: copy + tag remap. */
|
||||
int sz = (int)rt->size;
|
||||
const char *scrn = mklabel(c, "retscr");
|
||||
int scr = local_alloc(c, locals, scrn,
|
||||
sz, cg_frame);
|
||||
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||
for (int k = 0; k < sz; k += 8)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, scr + k));
|
||||
cg_widen_tagged_store(c, locals, rt,
|
||||
n->lhs, scr, sz);
|
||||
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
||||
areg(D_AX));
|
||||
if (sz > 8)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 8),
|
||||
areg(D_DX));
|
||||
if (sz > 16)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, scr + 16),
|
||||
areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -255,14 +255,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; };
|
||||
@@ -271,6 +281,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -828,6 +828,133 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};", 3 },
|
||||
/* Struct variant of a tagged union at the call site. The arg is
|
||||
* an N_STRUCTLIT, the param is (str|point). Widening at the call
|
||||
* site must zero-fill the scratch slot, store each field at
|
||||
* slot+8+field_off, then push the slot words high→low. */
|
||||
{ "type point = struct { x: i32, y: i32 };\n"
|
||||
"fn classify(r: (str | point)) i32 = {\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 0 - s.len: i32;\n"
|
||||
" case let p: point => return p.x + p.y;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" return classify(point { x = 10, y = 20 });\n"
|
||||
"};", 30 },
|
||||
/* Struct variant passed as a typed local. Widening copies the
|
||||
* struct words from the local into the scratch slot at +8. */
|
||||
{ "type point = struct { x: i32, y: i32 };\n"
|
||||
"fn classify(r: (str | point)) i32 = {\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 0 - s.len: i32;\n"
|
||||
" case let p: point => return p.x + p.y;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let p: point = point { x = 11, y = 22 };\n"
|
||||
" return classify(p);\n"
|
||||
"};", 33 },
|
||||
/* let-init of a tagged-union local from a struct literal: the
|
||||
* field stores go into slot+8+field_off in-place; tag patched
|
||||
* last. */
|
||||
{ "type point = struct { x: i32, y: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (str | point) = point { x = 7, y = 35 };\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 0 - s.len: i32;\n"
|
||||
" case let p: point => return p.x + p.y;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};", 42 },
|
||||
/* Reassign a tagged-union local to a struct literal. Same path
|
||||
* as let-init but writing into an already-allocated slot. */
|
||||
{ "type point = struct { x: i32, y: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (str | point) = \"init\";\n"
|
||||
" r = point { x = 100, y = 23 };\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 0 - s.len: i32;\n"
|
||||
" case let p: point => return p.x + p.y;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};", 123 },
|
||||
/* Return a struct variant of the fn's tagged return type. The
|
||||
* scratch-slot path materialises the struct payload then loads
|
||||
* AX/DX/CX from it. */
|
||||
{ "type point = struct { x: i32, y: i32 };\n"
|
||||
"fn make() (str | point) = {\n"
|
||||
" return point { x = 12, y = 30 };\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: (str | point) = make();\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 0 - s.len: i32;\n"
|
||||
" case let p: point => return p.x + p.y;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};", 42 },
|
||||
/* Widen a smaller tagged union to a wider one across slot sizes
|
||||
* AND remapped variant indices. (i32 | rune) is 16B with i32 at
|
||||
* tag 0; (str | i32 | rune) is 24B with i32 at tag 1. The widen
|
||||
* path copies the slot words, zero-pads to 24B, then runs a
|
||||
* CMPQ-chain switch to remap src tag 0 → dst tag 1. */
|
||||
{ "fn classify(r: (str | i32 | rune)) i32 = {\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 1;\n"
|
||||
" case let n: i32 => return n;\n"
|
||||
" case let c: rune => return c: i32 + 100;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let inner: (i32 | rune) = 42: i32;\n"
|
||||
" return classify(inner);\n"
|
||||
"};", 42 },
|
||||
/* Same shape but the rune variant of inner exercises the tag
|
||||
* remap from src tag 1 → dst tag 2. The rune literal needs an
|
||||
* explicit `: rune` cast — `'A'` is an untyped rune and the
|
||||
* variant search picks the first variant that accepts it (i32,
|
||||
* which also accepts untyped runes). 'A' = 65 + 100 = 165. */
|
||||
{ "fn classify(r: (str | i32 | rune)) i32 = {\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 1;\n"
|
||||
" case let n: i32 => return n;\n"
|
||||
" case let c: rune => return c: i32 + 100;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let inner: (i32 | rune) = 'A': rune;\n"
|
||||
" return classify(inner);\n"
|
||||
"};", 165 },
|
||||
/* let-init of a wider tagged union from a smaller-tagged local. */
|
||||
{ "fn main() i32 = {\n"
|
||||
" let inner: (i32 | rune) = 42: i32;\n"
|
||||
" let r: (str | i32 | rune) = inner;\n"
|
||||
" match (r) {\n"
|
||||
" case let s: str => return 1;\n"
|
||||
" case let n: i32 => return n;\n"
|
||||
" case let c: rune => return c: i32 + 100;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};", 42 },
|
||||
/* Spread variant in tagged-union type — `(...inner | T)`. The
|
||||
* checker flattens the spread's variants into the enclosing
|
||||
* union so `outer` has variants {i32, rune, str}. */
|
||||
{ "type inner = (i32 | rune);\n"
|
||||
"type outer = (...inner | str);\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let r: outer = 42: i32;\n"
|
||||
" match (r) {\n"
|
||||
" case let n: i32 => return n;\n"
|
||||
" case let c: rune => return c: i32;\n"
|
||||
" case let s: str => return 0;\n"
|
||||
" };\n"
|
||||
" return -1;\n"
|
||||
"};", 42 },
|
||||
/* Plan 9-style sentinel error idiom: `def NAME: error = "lit"`
|
||||
* inlines as the (ptr, len) pair at use sites. */
|
||||
{ "type error = str;\n"
|
||||
|
||||
Reference in New Issue
Block a user