check: type the N_BIN operand of as in the wwstage

An enum OR-fold under `as` ((m.A|m.B) as u32) was never typed:
scruttype resolves IDENT/DOT only, and the post-order restamp then
re-derived the folded member literals as untyped_int. The unstamped
operand missed cgtypeassert's #27b enum-reinterpret gate and lowered
as a phantom tagged assert — unconditional exit 1 at runtime on the
wwstage leg. checkisas now types an N_BIN lhs and the N_BIN restamp
preserves an existing enum stamp. Graduates the #59.9 stattest pin.
This commit is contained in:
2026-08-08 00:59:02 +09:00
parent c421c2b20a
commit 125f626697
4 changed files with 51 additions and 5 deletions

View File

@@ -1,13 +1,13 @@
package wwfixture;
def protocolversion: i32 = 1;
def corpuscount: i32 = 1236;
def corpuscount: i32 = 1237;
def errorcount: i32 = 314;
def compilecount: i32 = 12;
def runcount: i32 = 144;
def runcount: i32 = 145;
def runexitcount: i32 = 766;
def nativecount: i32 = 2472;
def corpushash: str = "4c8f4a693d2d8193b15f4215610c1d070dc83493b1189af772ca09cad4a69db7";
def nativecount: i32 = 2474;
def corpushash: str = "7763f2eb34524f67355114f5bdfbf3f64415152a8ec64060fe32a322bdbe1b24";
type directive = enum i32 {
ERROR = 0,

View File

@@ -3328,6 +3328,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
};
if (k == syntax.nkind.N_BIN) {
let tn: *syntax.node = binoptype(c, e);
// #59.9: checkisas pre-stamps an enum OR-fold (`(m.A|m.B) as
// u32`) TY_ENUM and folds the member N_DOTs to int literals;
// this post-order revisit re-derives from those now-untyped
// literals and clobbered the stamp, so cgtypeassert's #27b
// reinterpret gate missed and emitted a phantom tagged assert
// (unconditional exit 1). Keep an existing enum stamp; the
// returned tnode contract for callers is unchanged.
let pre599: *syntax.tinfo = e.type_: *syntax.tinfo;
if (pre599 != nil) {
let prech: *syntax.tinfo = tichase(pre599);
if (prech != nil) {
if (prech.kind == syntax.tykind.TY_ENUM) {
return tn;
};
};
};
e.type_ = tinfofornode(c, tn): *void;
return tn;
};
@@ -6353,6 +6369,15 @@ fn checkisas(c: *checker, n: *syntax.node) void = {
if (n.lhs.kind == syntax.nkind.N_TRYPROP || n.lhs.kind == syntax.nkind.N_TRYUNW) {
st = exprtype(c, n.lhs, nil);
};
// #59.9: an N_BIN operand (`(m.A | m.B) as u32`) was never
// typed, so the OR-fold reached cgen unstamped and
// cgtypeassert's enum-reinterpret gate (#27b) missed —
// lowering a plain enum value as a phantom tagged assert
// (unconditional exit 1). cstage types the lhs
// unconditionally (cmd/wcc/check.c:2220 cexpr(c, n->lhs)).
if (n.lhs.kind == syntax.nkind.N_BIN) {
st = exprtype(c, n.lhs, nil);
};
};
let u: *syntax.node = resolvealias(c, unwrapbang(st));
if (u == nil) { return; };

View File

@@ -169,8 +169,13 @@ static const struct ent ents[] = {
/* #59.7 siphash graduated to M_ID above (#61 fix) */
{ .fixture = "lib/log/logtest.ww",
.mode = M_DIVERGE, .cite = "#59.8" },
/* #59.9 graduated: the wwstage checker never typed an N_BIN operand
* of `as`, so an enum OR-fold reached cgen unstamped and lowered as
* a phantom tagged assert (unconditional exit 1). checkisas now
* types the N_BIN lhs and the post-order restamp preserves the enum
* stamp. Runtime pin: r599_enum_orfold_as. */
{ .fixture = "lib/os/stattest.ww",
.mode = M_DIVERGE, .cite = "#59.9" },
.mode = M_ID, .cite = "#59.9 graduated by the checkisas N_BIN stamp" },
/* #59.10 stoftest graduated to M_ID above (#62 fix) */
{ .fixture = "lib/ww/syntax/toktest.ww", .inc = "lib/ww",
.mode = M_ID, .cite = "#59.11 graduated by #146 str==" },

View File

@@ -0,0 +1,16 @@
//ww:run
// #59.9: an enum member OR-fold under `as`. The pre-fix wwstage
// checker never typed the N_BIN operand, so cgtypeassert's enum
// reinterpret gate missed and lowered a phantom tagged assert that
// unconditionally exited 1.
package main;
type mask = enum u32 { A = 1, B = 2, C = 4 };
fn main() i32 = {
let w: u32 = (mask.A | mask.B | mask.C) as u32;
if (w != 7u32) { return 1; };
let v: u32 = (mask.A | mask.C) as u32;
if (v != 5u32) { return 2; };
return 0;
};