wcc: stamp computed enum-member value-exprs

A computed enum member — B = A + 4, RW = R | W, sibling/chained backref —
left its value-expr node nil-typed: enumvalfold folds the constant but never
stamps the expr, and since enum members are not installed as scope idents the
sibling backref resolves to nothing, so BOTH the N_BIN/N_UN wrapper and the
backref N_IDENT go nil (literal members are fine). Stamp the value-expr subtree
(only-nil) to the enum's underlying storage type via a new
stampenumvals/stampnilexpr pass on the N_TENUM branch. Mirrors harec checking
each member value-expr at the underlying type (ref/harec/src/check.c:4419).

A prerequisite for arming the wwstage asserttyped bail. Checker-only — the
value folds to a constant at every use site and in cgen, so the node's type_
is never read by codegen; 990-997 byte-id hold. Extends the 901 gap-corpus
with 901_enum_corpus.ww.
This commit is contained in:
2026-05-28 08:07:19 +09:00
parent 360b58b267
commit 3252bd1709
5 changed files with 158 additions and 1 deletions

View File

@@ -10522,6 +10522,8 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
if (k == nkind.N_TENUM) { stampenumvals(c, n); };
// #42's size/align/offset fold trigger lived here pre-A.6.0; the
// A.6.0 end-of-fn general dispatch (below) now fires exprtype on
// every N_CALL — same context-free coverage, one dispatch site.
@@ -11334,6 +11336,43 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
return false;
};
// stampenumvals — give every node in each enum-member value-expr a
// non-nil type_. resolvewalk's post-order exprtype (L543) stamps the
// literal leaves, but a sibling backref (`B = A + 4`) resolves to
// nothing — enum members aren't installed as scope idents — so the
// backref N_IDENT and the N_BIN/N_UN wrapping it stay nil. asserttyped
// walks the enum DEFINITION (whether or not a member is `.`-accessed)
// and its value-node invariant then fires on those. harec checks each
// member's value-expr at the enum's underlying type
// (ref/harec/src/check.c:4419 — check_expression with type->alias.type),
// so the whole constant subtree carries the underlying integer type;
// mirror that. The value itself is folded to a constant at every use
// site (enumvalfold) and at codegen (cgen.ww enumevalmember), so cgen
// never reads these node types — this stamp is checker metadata only.
fn stampenumvals(c: *checker, n: *node) void = {
let under: *tinfo = c.tc.tyi32;
if (n.lhs != nil) {
let s: *tinfo = tinfofornode(c, n.lhs);
if (s != nil) { under = s; };
};
let m: *node = n.list;
for (m != nil) {
stampnilexpr(m.lhs, under);
m = m.next;
};
};
// stampnilexpr — stamp nil-typed nodes in a constant expr subtree to
// `ti`. lhs/rhs cover the enum constexpr grammar enumvalfold accepts
// (literals, unary, binary, sibling backref); non-nil nodes keep the
// type exprtype already derived.
fn stampnilexpr(n: *node, ti: *tinfo) void = {
if (n == nil) { return; };
if (n.type_ == nil) { n.type_ = ti: *void; };
stampnilexpr(n.lhs, ti);
stampnilexpr(n.rhs, ti);
};
// #61 A.5 helper: per-element slot size when `pt` appears inside a
// tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI
// spills each element into its own register / 8B eightbyte, so narrow

View File

@@ -492,6 +492,8 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
if (k == nkind.N_TENUM) { stampenumvals(c, n); };
// #42's size/align/offset fold trigger lived here pre-A.6.0; the
// A.6.0 end-of-fn general dispatch (below) now fires exprtype on
// every N_CALL — same context-free coverage, one dispatch site.
@@ -1304,6 +1306,43 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
return false;
};
// stampenumvals — give every node in each enum-member value-expr a
// non-nil type_. resolvewalk's post-order exprtype (L543) stamps the
// literal leaves, but a sibling backref (`B = A + 4`) resolves to
// nothing — enum members aren't installed as scope idents — so the
// backref N_IDENT and the N_BIN/N_UN wrapping it stay nil. asserttyped
// walks the enum DEFINITION (whether or not a member is `.`-accessed)
// and its value-node invariant then fires on those. harec checks each
// member's value-expr at the enum's underlying type
// (ref/harec/src/check.c:4419 — check_expression with type->alias.type),
// so the whole constant subtree carries the underlying integer type;
// mirror that. The value itself is folded to a constant at every use
// site (enumvalfold) and at codegen (cgen.ww enumevalmember), so cgen
// never reads these node types — this stamp is checker metadata only.
fn stampenumvals(c: *checker, n: *node) void = {
let under: *tinfo = c.tc.tyi32;
if (n.lhs != nil) {
let s: *tinfo = tinfofornode(c, n.lhs);
if (s != nil) { under = s; };
};
let m: *node = n.list;
for (m != nil) {
stampnilexpr(m.lhs, under);
m = m.next;
};
};
// stampnilexpr — stamp nil-typed nodes in a constant expr subtree to
// `ti`. lhs/rhs cover the enum constexpr grammar enumvalfold accepts
// (literals, unary, binary, sibling backref); non-nil nodes keep the
// type exprtype already derived.
fn stampnilexpr(n: *node, ti: *tinfo) void = {
if (n == nil) { return; };
if (n.type_ == nil) { n.type_ = ti: *void; };
stampnilexpr(n.lhs, ti);
stampnilexpr(n.rhs, ti);
};
// #61 A.5 helper: per-element slot size when `pt` appears inside a
// tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI
// spills each element into its own register / 8B eightbyte, so narrow

View File

@@ -10522,6 +10522,8 @@ fn resolvewalk(c: *checker, n: *node) void = {
};
};
if (k == nkind.N_TENUM) { stampenumvals(c, n); };
// #42's size/align/offset fold trigger lived here pre-A.6.0; the
// A.6.0 end-of-fn general dispatch (below) now fires exprtype on
// every N_CALL — same context-free coverage, one dispatch site.
@@ -11334,6 +11336,43 @@ fn enumvalfold(body: *node, until: *node, e: *node, out: *u64) bool = {
return false;
};
// stampenumvals — give every node in each enum-member value-expr a
// non-nil type_. resolvewalk's post-order exprtype (L543) stamps the
// literal leaves, but a sibling backref (`B = A + 4`) resolves to
// nothing — enum members aren't installed as scope idents — so the
// backref N_IDENT and the N_BIN/N_UN wrapping it stay nil. asserttyped
// walks the enum DEFINITION (whether or not a member is `.`-accessed)
// and its value-node invariant then fires on those. harec checks each
// member's value-expr at the enum's underlying type
// (ref/harec/src/check.c:4419 — check_expression with type->alias.type),
// so the whole constant subtree carries the underlying integer type;
// mirror that. The value itself is folded to a constant at every use
// site (enumvalfold) and at codegen (cgen.ww enumevalmember), so cgen
// never reads these node types — this stamp is checker metadata only.
fn stampenumvals(c: *checker, n: *node) void = {
let under: *tinfo = c.tc.tyi32;
if (n.lhs != nil) {
let s: *tinfo = tinfofornode(c, n.lhs);
if (s != nil) { under = s; };
};
let m: *node = n.list;
for (m != nil) {
stampnilexpr(m.lhs, under);
m = m.next;
};
};
// stampnilexpr — stamp nil-typed nodes in a constant expr subtree to
// `ti`. lhs/rhs cover the enum constexpr grammar enumvalfold accepts
// (literals, unary, binary, sibling backref); non-nil nodes keep the
// type exprtype already derived.
fn stampnilexpr(n: *node, ti: *tinfo) void = {
if (n == nil) { return; };
if (n.type_ == nil) { n.type_ = ti: *void; };
stampnilexpr(n.lhs, ti);
stampnilexpr(n.rhs, ti);
};
// #61 A.5 helper: per-element slot size when `pt` appears inside a
// tuple. Mirrors cgenutil.ww slotsize TTUPLE — cstage's tuple ABI
// spills each element into its own register / 8B eightbyte, so narrow