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

@@ -52,7 +52,7 @@ export fn avail(b: *buf) i32 = {
// Distinct alias so `(str | linerr)` has two variant types the
// tagged-union machinery can keep apart at the tag level. The error
// variant carries a short description; callers compare with errors.is
// variant carries a short description; callers compare with errors.equal
// or just inspect by length.
type linerr = str;

View File

@@ -16,10 +16,10 @@ export fn isnil(e: error) bool = {
return e.len == 0;
};
// is — compare an error against a sentinel (or any other error). Pure
// byte equality; same shape as strings.equal but kept here so callers
// don't have to pull in strings just to compare.
export fn is(e: error, want: error) bool = {
// equal — compare an error against a sentinel (or any other error).
// Pure byte equality. (Was `errors.is` before `is` became a keyword
// for tagged-union type-tests; rename matches bytes.equal / strings.equal.)
export fn equal(e: error, want: error) bool = {
if (e.len != want.len) { return false; };
let i: i32 = 0;
for (i < e.len) {

View File

@@ -84,7 +84,12 @@ def N_TRYUNW: i32 = 57;
def N_MLET: i32 = 58;
def N_MASSIGN: i32 = 59;
def N_LAST: i32 = 60;
// Appended at the tail to keep all prior N_* values stable. The
// 990_selfhost test diffs astprint against the C side byte-for-byte.
def N_TYPETEST: i32 = 60;
def N_TYPEASSERT: i32 = 61;
def N_LAST: i32 = 62;
// ---- Node -------------------------------------------------------------
@@ -183,6 +188,8 @@ fn nkname(k: i32) str = {
if (k == N_TRYUNW) { return "tryunw"; };
if (k == N_MLET) { return "mlet"; };
if (k == N_MASSIGN) { return "massign"; };
if (k == N_TYPETEST) { return "typetest"; };
if (k == N_TYPEASSERT) { return "typeassert"; };
if (k == N_LAST) { return "last"; };
return "?";
};

View File

@@ -105,7 +105,12 @@ def TK_LARROW: i32 = 79;
def TK_ARROW: i32 = 80;
def TK_FATARROW: i32 = 81;
def TK_LAST: i32 = 82;
// Appended at the tail (not grouped with the keyword block) so every
// pre-existing TK_* value stays unchanged — the 990_selfhost test
// diffs wwdump output against the C side, byte for byte.
def TK_IS: i32 = 82;
def TK_LAST: i32 = 83;
// ---- Pos / Tok --------------------------------------------------------
//
@@ -164,6 +169,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
if (streqn(p, "fn", n)) { return TK_FN; };
if (streqn(p, "for", n)) { return TK_FOR; };
if (streqn(p, "if", n)) { return TK_IF; };
if (streqn(p, "is", n)) { return TK_IS; };
if (streqn(p, "let", n)) { return TK_LET; };
if (streqn(p, "match", n)) { return TK_MATCH; };
if (streqn(p, "nil", n)) { return TK_NIL; };
@@ -215,6 +221,7 @@ export fn tokname(k: i32) str = {
if (k == TK_TRUE) { return "true"; };
if (k == TK_FALSE) { return "false"; };
if (k == TK_AS) { return "as"; };
if (k == TK_IS) { return "is"; };
if (k == TK_STATIC) { return "static"; };
if (k == TK_MATCH) { return "match"; };
if (k == TK_CONST) { return "const"; };

View File

@@ -306,6 +306,26 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
cur = n;
continue;
};
// Hare-style postfix:
// `e as T` — assert lhs is variant T (abort otherwise) → T
// `e is T` — bool: does lhs currently hold variant T?
// Same precedence level as the `:` cast.
if (p.curkind == TK_AS) {
advance(p);
let n: *node = newnode(p.a, N_TYPEASSERT, pf, pl, pc);
n.lhs = cur;
n.rhs = parsetype(p);
cur = n;
continue;
};
if (p.curkind == TK_IS) {
advance(p);
let n: *node = newnode(p.a, N_TYPETEST, pf, pl, pc);
n.lhs = cur;
n.rhs = parsetype(p);
cur = n;
continue;
};
break;
};
return cur;