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:
@@ -1936,6 +1936,71 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
case N_TYPETEST: {
|
||||
/* `e is T` — Compare scrutinee tag against T's variant index.
|
||||
* Result is bool (0/1) in AX. We only need the tag, not the
|
||||
* value words, so cgexpr's AX-only result is enough for both
|
||||
* register-returning callers (AX=tag) and N_IDENT locals
|
||||
* (cgexpr loads slot+0 → AX, which is the tag). */
|
||||
cgexpr(c, n->lhs, locals);
|
||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||
int tag = cg_tag_for_variant(u, vt);
|
||||
char *ne = mklabel(c, "is_ne");
|
||||
char *done = mklabel(c, "is_done");
|
||||
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||
ins1(c, A_JNE, abranch(ne));
|
||||
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
||||
ins1(c, A_JMP, abranch(done));
|
||||
label(c, ne);
|
||||
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
|
||||
label(c, done);
|
||||
break;
|
||||
}
|
||||
case N_TYPEASSERT: {
|
||||
/* `e as T` — abort if tag != T's variant index; otherwise
|
||||
* unwrap value to T's ABI: scalar/ptr variants land in AX;
|
||||
* 16B str variants in AX:BX.
|
||||
*
|
||||
* We need both tag *and* value words. For an N_IDENT local
|
||||
* the value lives at slot+8/+16 — cgexpr's single-MOVQ path
|
||||
* does not load it. Mirror match's pattern: resolve a slot
|
||||
* offset (existing local or a fresh @asrt_spill) and index
|
||||
* out tag/value from memory. */
|
||||
Node *s = n->lhs;
|
||||
Type *st = s ? s->type : NULL;
|
||||
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
Type *vt = n->type;
|
||||
int tag = cg_tag_for_variant(u, vt);
|
||||
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
|
||||
int sl_off = 0;
|
||||
if (s && s->kind == N_IDENT && s->str) {
|
||||
sl_off = localfind(locals, s->str);
|
||||
}
|
||||
if (sl_off == 0) {
|
||||
sl_off = localoff(c, &locals, "@asrt_spill",
|
||||
slot_size, cg_frame);
|
||||
cgexpr(c, s, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
|
||||
if (slot_size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, sl_off + 16));
|
||||
}
|
||||
char *ok = mklabel(c, "asrt_ok");
|
||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX));
|
||||
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
|
||||
ins1(c, A_JE, abranch(ok));
|
||||
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
|
||||
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||
ins0(c, A_SYSCALL);
|
||||
label(c, ok);
|
||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 8), areg(D_AX));
|
||||
if (type_isstr(vt))
|
||||
ins2(c, A_MOVQ, amem(D_BP, sl_off + 16), areg(D_BX));
|
||||
break;
|
||||
}
|
||||
case N_CAST: {
|
||||
int from_f = node_isfloat(n->lhs);
|
||||
int to_f = cg_isfloat(n->type);
|
||||
|
||||
@@ -81,6 +81,8 @@ nkname(Nkind k)
|
||||
case N_TRYUNW: return "tryunw";
|
||||
case N_MLET: return "mlet";
|
||||
case N_MASSIGN: return "massign";
|
||||
case N_TYPETEST: return "typetest";
|
||||
case N_TYPEASSERT: return "typeassert";
|
||||
case N_LAST: return "last";
|
||||
}
|
||||
return "?";
|
||||
|
||||
@@ -710,6 +710,48 @@ cexpr(Checker *c, Node *n)
|
||||
n->type = ty_void;
|
||||
return n->type;
|
||||
}
|
||||
case N_TYPETEST: case N_TYPEASSERT: {
|
||||
/* `e is T` → bool; `e as T` → T.
|
||||
* Requires lhs to be a tagged union and T to be one of its
|
||||
* variants. The variant-index lookup lives in cgen (it knows
|
||||
* NAMED-vs-structural matching for the success-variant rules);
|
||||
* here we just check the LHS shape and resolve T. */
|
||||
Type *t = cexpr(c, n->lhs);
|
||||
Type *vt = resolve_type(c, n->rhs);
|
||||
/* Stash the variant on rhs->type — cgen reads it uniformly
|
||||
* whether the expression returns bool (is) or the variant
|
||||
* itself (as). */
|
||||
if (n->rhs) n->rhs->type = vt;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (u == NULL || u->kind != TY_TAGGED) {
|
||||
const char *op = (n->kind == N_TYPETEST) ? "is" : "as";
|
||||
return n->type = err(c, n->pos,
|
||||
"%s on non-tagged-union %s", op,
|
||||
type_name(c->a, t));
|
||||
}
|
||||
/* Diagnostic-only: verify T appears as a variant. Mirrors
|
||||
* cg_variant_match (NAMED ≡ pointer-identical, otherwise
|
||||
* structural). Skipped silently if vt is ty_err. */
|
||||
if (vt && vt != ty_err) {
|
||||
int found = 0;
|
||||
for (Tparam *p = u->params; p; p = p->next) {
|
||||
if (p->type == NULL) continue;
|
||||
if (p->type->kind == TY_NAMED &&
|
||||
vt->kind == TY_NAMED) {
|
||||
if (p->type == vt) { found = 1; break; }
|
||||
} else if (p->type->kind == TY_NAMED ||
|
||||
vt->kind == TY_NAMED) {
|
||||
continue;
|
||||
} else if (type_eq(p->type, vt)) {
|
||||
found = 1; break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
err(c, n->pos, "%s is not a variant of %s",
|
||||
type_name(c->a, vt), type_name(c->a, t));
|
||||
}
|
||||
return n->type = (n->kind == N_TYPETEST) ? ty_bool : vt;
|
||||
}
|
||||
case N_TRYPROP: case N_TRYUNW: {
|
||||
Type *t = cexpr(c, n->lhs);
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
|
||||
@@ -670,6 +670,22 @@ parsepostfix(Parser *p, Node *lhs)
|
||||
lhs = n;
|
||||
break;
|
||||
}
|
||||
case TK_AS:
|
||||
case TK_IS: {
|
||||
/* Hare-style:
|
||||
* e as T — assert lhs is variant T of its tagged
|
||||
* union; abort if not. Yields T.
|
||||
* e is T — bool: does lhs currently hold variant T?
|
||||
* Postfix, same level as `:` cast. */
|
||||
Tkind k = p->cur.kind;
|
||||
advance(p);
|
||||
Node *n = newnode(p->a,
|
||||
k == TK_AS ? N_TYPEASSERT : N_TYPETEST, pp);
|
||||
n->lhs = lhs;
|
||||
n->rhs = parsetype(p);
|
||||
lhs = n;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return lhs;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ static const struct kwent kwtab[] = {
|
||||
{ "fn", TK_FN },
|
||||
{ "for", TK_FOR },
|
||||
{ "if", TK_IF },
|
||||
{ "is", TK_IS },
|
||||
{ "let", TK_LET },
|
||||
{ "match", TK_MATCH },
|
||||
{ "nil", TK_NIL },
|
||||
@@ -89,6 +90,7 @@ tokname(Tkind k)
|
||||
case TK_TRUE: return "true";
|
||||
case TK_FALSE: return "false";
|
||||
case TK_AS: return "as";
|
||||
case TK_IS: return "is";
|
||||
case TK_STATIC: return "static";
|
||||
case TK_MATCH: return "match";
|
||||
case TK_CONST: return "const";
|
||||
|
||||
13
cmd/wcc/ww.h
13
cmd/wcc/ww.h
@@ -111,7 +111,7 @@ typedef enum {
|
||||
TK_NIL,
|
||||
TK_TRUE,
|
||||
TK_FALSE,
|
||||
TK_AS, /* reserved for future cast spelling, not active */
|
||||
TK_AS, /* Hare-style type assertion: e as T */
|
||||
TK_STATIC, /* Hare-style storage-class qualifier */
|
||||
TK_MATCH, /* match expression head */
|
||||
TK_CONST, /* const binding */
|
||||
@@ -172,6 +172,11 @@ typedef enum {
|
||||
TK_ARROW, /* -> (reserved) */
|
||||
TK_FATARROW, /* => (match arms) */
|
||||
|
||||
/* Appended after TK_FATARROW (not grouped with the keyword block)
|
||||
* to keep the numeric value of every existing kind unchanged —
|
||||
* the selfhost wwdump-diff test (990) is byte-sensitive. */
|
||||
TK_IS, /* Hare-style type test: e is T */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
} Tkind;
|
||||
|
||||
@@ -284,6 +289,12 @@ typedef enum {
|
||||
N_MLET, /* let a, b = expr; list = N_LET stubs (str, lhs=type), rhs = expr */
|
||||
N_MASSIGN, /* a, b = expr; list = lvalue exprs, rhs = expr */
|
||||
|
||||
/* Appended after N_MASSIGN — keeps the numeric value of every
|
||||
* existing kind unchanged, so the selfhost AST dump still diffs
|
||||
* byte-for-byte. lhs=value, rhs=variant type expr. */
|
||||
N_TYPETEST, /* lhs is T → bool */
|
||||
N_TYPEASSERT, /* lhs as T → T (abort if tag mismatch) */
|
||||
|
||||
N_LAST
|
||||
} Nkind;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user