diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index f2181347..0b87334b 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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 diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index db5747b7..8ce31b87 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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 diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 10833299..79b8ddc7 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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 diff --git a/test/wcc/901_asserttyped_gap.c b/test/wcc/901_asserttyped_gap.c index 0d252a71..377e0d0c 100644 --- a/test/wcc/901_asserttyped_gap.c +++ b/test/wcc/901_asserttyped_gap.c @@ -25,6 +25,7 @@ * C abort intrinsic callee utf8 8 * D module-leaf == type/fn name fnmatch 0 (closed) * D module-leaf == type/fn name random 0 (closed) + * E computed enum-member value-expr enum_corpus 0 (closed) * * Exit code of wwdump_ww is intentionally not gated: the diagnostics * land on stderr regardless of the run's success, and arming the bail @@ -116,6 +117,8 @@ main(void) "D module-leaf == type/fn name", 0 }, { "lib/math/random/random_test.combined.ww", "D module-leaf == type/fn name", 0 }, + { "test/wcc/901_enum_corpus.ww", + "E computed enum-member value-expr", 0 }, { NULL, NULL, 0 }, }; @@ -151,6 +154,6 @@ main(void) return 1; } printf("asserttyped_gap: ww-stage checker warn set matches manifest " - "on %d gap-corpus fixtures (C pinned, A+B+D closed)\n", n); + "on %d gap-corpus fixtures (C pinned, A+B+D+E closed)\n", n); return 0; } diff --git a/test/wcc/901_enum_corpus.ww b/test/wcc/901_enum_corpus.ww new file mode 100644 index 00000000..dfb8223e --- /dev/null +++ b/test/wcc/901_enum_corpus.ww @@ -0,0 +1,37 @@ +// 901_enum_corpus — computed enum-member value-expr gap fixture for +// the 901 asserttyped net (class E). Each computed member leaves its +// value-expr (N_BIN / N_UN / sibling-backref N_IDENT) type_=nil until +// the checker's stampenumvals fills the subtree at the enum's +// underlying type. Mirrors the 759_check_enum_fold rows plus the 994 +// in-source `RW = R | W` shape (enum u8). Literal + auto-increment +// members never warned (resolvewalk's post-order exprtype stamps the +// leaf), so the literal-only enum is a control: it must stay 0. +package main; + +type sib = enum i32 { A = 7, B = A }; +type un = enum i32 { NEG = -7, R = NEG + 14 }; +type unl = enum i32 { TIL = ~(-8), PLU = +7 }; +type arith = enum i32 { + A = 3, + ADD = A + 4, + SUB = A - 1, + MUL = A * 2, + DIV = A / 3, + MOD = A % 2, +}; +type bits = enum i32 { + A = 5, + AND = A & 3, + IOR = A | 2, + XOR = A ^ 1, + SHL = A << 2, + SHR = A >> 1, +}; +type chain = enum i32 { A = 1, B = A + 1, C = B + 1, D = C + 4 }; +type mode = enum u8 { RD = 1u8, WR = 2u8, RW = RD | WR }; + +type lit = enum i32 { X = 7, Y = 9, Z = 11 }; + +fn main() i32 = { + return chain.D: i32 + mode.RW: i32; +};