ww: add Hare-style is/as postfix ops on tagged unions

`e is T` returns bool (variant tag == T's index); `e as T` unwraps
to T or exit(1) on mismatch. Postfix, same precedence as `:` cast.
TK_IS / N_TYPETEST / N_TYPEASSERT appended at the tail of their
enums so every prior numeric value stays unchanged — the
990_selfhost wwdump-diff stays byte-clean.

Cgen mirrors the match-case slot-based load (tag at +0, value at
+8/+16), so an N_IDENT tagged-union local works just like a
match scrutinee. Selfhost cgen inlines the slot resolution
because the wwstage cgen drops sign bits on `*i32` output
parameters in this position.

Renames `errors.is` -> `errors.equal` (the only naming collision;
the existing comment already noted it shared shape with
strings.equal/bytes.equal).
This commit is contained in:
2026-05-11 23:21:08 +09:00
parent 8899ce5621
commit dd188ca460
16 changed files with 614 additions and 15 deletions

View File

@@ -79,6 +79,115 @@ fn cgexpr(c: *cgen, n: *node) void = {
if (k == N_CALL) { cgcall(c, n); return; };
if (k == N_ASSIGN) { cgassign(c, n); return; };
if (k == N_TYPETEST) { cgtypetest(c, n); return; };
if (k == N_TYPEASSERT) { cgtypeassert(c, n); return; };
};
// cgtagvariantidx — find the 0-based variant index of `vt` inside the
// tagged-union type expression `tagged`. -1 if `tagged` isn't an
// N_TTAGGED or no variant matches. Mirrors the lookup that cgmatch
// does inline; pulled out so `is` / `as` can reuse it.
fn cgtagvariantidx(tagged: *node, vt: *node) i32 = {
if (tagged == nil) { return -1; };
if (vt == nil) { return -1; };
if (tagged.kind != N_TTAGGED) { return -1; };
let want: str;
want.ptr = nil; want.len = 0;
if (vt.kind == N_TNAME) { want = vt.str; };
if (want.len == 0) { return -1; };
let v: *node = tagged.list;
let idx: i32 = 0;
for (v != nil) {
if (v.kind == N_TNAME) {
if (streq(v.str, want)) { return idx; };
};
v = v.next;
idx += 1;
};
return -1;
};
fn cgtypetest(c: *cgen, n: *node) void = {
// `e is T` — load the lhs's tag, compare against T's variant
// index, set AX = (tag == idx). Result type is bool.
//
// Slot resolution is inlined (rather than factored into a helper
// with output parameters): wwstage cgen has a trap with i32
// stored via *i32 in this context — direct assignment of the
// local works, indirection through &scrutoff drops sign bits.
let lhs: *node = n.lhs;
let scrutoff: i32 = 0;
let scrutt: *node = nil;
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let lc: *local = localfindnode(c, lhs.str);
if (lc != nil) {
scrutoff = lc.off;
scrutt = resolvetype(c, lc.tnode);
};
};
};
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
if (want < 0) { want = 0; };
emitline("\tMOVQ\t");
emitoff(scrutoff: i64);
emitline("(BP), AX\n");
let nel: str = mklabel(c, "is_ne");
let dnl: str = mklabel(c, "is_done");
emitline("\tCMPQ\t$");
emitint(want: i64);
emitline(", AX\n");
emitline("\tJNE\t");
emitline(nel);
emitline("\n\tMOVQ\t$1, AX\n\tJMP\t");
emitline(dnl);
emitline("\n");
emitlabel(nel);
emitline("\tMOVQ\t$0, AX\n");
emitlabel(dnl);
return;
};
fn cgtypeassert(c: *cgen, n: *node) void = {
// `e as T` — load tag, abort (exit 1) if tag != T's variant
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
// Slot resolution inlined; see cgtypetest comment.
let lhs: *node = n.lhs;
let scrutoff: i32 = 0;
let scrutt: *node = nil;
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let lc: *local = localfindnode(c, lhs.str);
if (lc != nil) {
scrutoff = lc.off;
scrutt = resolvetype(c, lc.tnode);
};
};
};
let want: i32 = cgtagvariantidx(scrutt, n.rhs);
if (want < 0) { want = 0; };
let okl: str = mklabel(c, "asrt_ok");
emitline("\tMOVQ\t");
emitoff(scrutoff: i64);
emitline("(BP), AX\n");
emitline("\tCMPQ\t$");
emitint(want: i64);
emitline(", AX\n");
emitline("\tJE\t");
emitline(okl);
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
emitlabel(okl);
emitline("\tMOVQ\t");
emitoff((scrutoff + 8): i64);
emitline("(BP), AX\n");
if (isstrtype(c, n.rhs)) {
emitline("\tMOVQ\t");
emitoff((scrutoff + 16): i64);
emitline("(BP), BX\n");
};
return;
};
fn cgstrlit(c: *cgen, n: *node) void = {