Files
ww/test/wcc/data/attest_pass.ww
Hojun-Cho 00b9933ee0 wcc/cgen: enum as int is a value conversion, not a tagged assertion (ww stage)
The value-passthrough arm gated on node shape (isenumexpr); a
constant-folded enum member reaches cgen as an int-literal node,
missed the arm, and fell into the tagged-union assertion path
(CMPQ $0 + JNE -> exit 1) — silent wrong code for every `enum as
int`, fnmatch's flag tests included. Gate on the stamped operand
type (tichase == TY_ENUM) at the cgtypeassert choke point, mirroring
cstage cgen.c N_TYPEASSERT; dead node-shape helpers deleted. Tagged
`as` assertion path byte-id-unmoved (control rows). Six @test pins
in attest_pass.ww, dual-stage. (#27b-team, cat-A)
2026-06-11 02:37:32 +09:00

230 lines
7.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @test fixture: every test passes (exits without aborting).
package data;
import rt; // #3/B': check_empty_alloc_annotated calls alloc → rt_malloc.
type point = struct { x: i32, y: i32 };
// #27b: enum bases for the `<enum> as <inttype>` value-reinterpret pins
// below (multiple enum bases × int widths).
type eflag = enum i32 { A = 1, B = 2, C = 4 };
type ebyte = enum u8 { LO = 1, HI = 200 };
type eplain = enum { Z = 0, ONE = 1, TWO = 2 };
// #87: a PLAIN (non-alias) module-level tagged-union global SEGV'd BOTH
// stages — no static DATA emitted (read as a frame-local at BP) + the
// match scrutinee read saved BP as the tag. Fix: emit the 32B box that
// byte-MIRRORS a runtime LOCAL (tag@0 | payload@8 | zero-pad) + resolve
// the match scrutinee via LEAQ name(SB). These globals back the rob §3
// global-vs-local byte-identity PIN below; the tuple global locks the
// already-correct #86 path against any emitter regression.
let g87_int: (i32 | str) = 42;
let g87_str: (i32 | str) = "x";
let g86_tup: (u32, u32) = (1u32, 2u32);
@test fn check_add() void = {
let a: i32 = 2;
let b: i32 = 3;
let c: i32 = a + b;
if (c != 5) {
let _: i32 = 1 / 0; // abort via div-by-zero would also work
};
};
@test fn check_match() void = {
let r: (i32 | str) = 7;
let v: i32 = match (r) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
if (v != 7) {
let _: i32 = 1 / 0;
};
};
// #227: compound assign (`-=`/`+=`) on a local field must
// load-combine-store, not drop the op. Pre-fix wwstage stored the bare
// rhs (p.x->4, p.y->5, view.len->1), so each mismatch aborts via 1/0.
@test fn check_local_field_compound() void = {
let p: point = point { x = 10i32, y = 3i32 };
p.x -= 4i32;
p.y += 5i32;
if (p.x != 6) {
let _: i32 = 1 / 0;
};
if (p.y != 8) {
let _: i32 = 1 / 0;
};
let view: str = "hello";
view.len -= 1;
let n: i32 = view.len: i32;
if (n != 4) {
let _: i32 = 1 / 0;
};
};
// #264: a match-bound binder used inside a `yield` arm of a match-AS-
// EXPRESSION. Pre-fix the wwstage checker stamped the operand in-scope
// during the N_MCASE arm walk, then RE-TYPED it out of the (popped) arm
// scope while deriving the match's yield type (exprtype N_MATCH →
// matchyieldtype). Out of scope the operand couldn't re-resolve, the
// re-derive returned nil, and the N_UN/N_BIN/N_INDEX restamp arms
// overwrote the good in-scope stamp with nil → asserttyped:un/bin/index
// aborted (cstage accepted it). Root fix: matchyieldtype now READS the
// cached operand tinfo at the post-walk call instead of re-deriving
// (mirrors cstage match_yield_type reading body->lhs->type, check.c:121),
// so no operand shape can be clobbered. These rows pin the whole operand
// class by construction: deref (*p), bin (*p+1), slice-index (p[i]), and
// deref-then-field ((*p).x). (The *[N]T ptr-to-array index variant is
// blocked separately by #278's exhaustiveness false-reject, so it uses a
// []i32 slice binder here.)
@test fn check_match_ptr_deref() void = {
let n: i32 = 42i32;
let xi: (*i32 | void) = &n;
let v: i32 = match (xi) {
case let p: *i32 => yield *p;
case void => yield -1i32;
};
if (v != 42) {
let _: i32 = 1 / 0;
};
// N_BIN operand: `yield *p + 1`.
let m: i32 = 9i32;
let xb: (*i32 | void) = &m;
let vb: i32 = match (xb) {
case let p: *i32 => yield *p + 1i32;
case void => yield -1i32;
};
if (vb != 10) {
let _: i32 = 1 / 0;
};
// N_INDEX operand: `yield p[1]` over a []i32 binder.
let arr: [3]i32 = [4i32, 5i32, 6i32];
let sl: []i32 = arr;
let xs: ([]i32 | void) = sl;
let vs: i32 = match (xs) {
case let p: []i32 => yield p[1];
case void => yield -1i32;
};
if (vs != 5) {
let _: i32 = 1 / 0;
};
let pt: point = point { x = 7i32, y = 9i32 };
let xp: (*point | void) = &pt;
let w: i32 = match (xp) {
case let p: *point => yield (*p).x;
case void => yield -1i32;
};
if (w != 7) {
let _: i32 = 1 / 0;
};
};
// #3/B': the let-annotation context path keeps `alloc([], n)` inferring
// its element type for ANY T (u8 default + non-u8 #45 retype). The bare /
// return / arg empty allocs that lost the silent u8 default are covered by
// the negative test (test/wcc/729). Here we pin that annotated allocs of
// both an 8-bit and a wide element still compile and index correctly.
// alloc([], n) is Hare's len=0 / cap=n empty slice, so we write into the
// cap-backed memory and read it back (ref/hare expects len 0, not n).
@test fn check_empty_alloc_annotated() void = {
let b: []u8 = alloc([], 8u64)!;
b[0] = 7u8;
b[3] = 9u8;
if (b[0] != 7 || b[3] != 9) {
let _: i32 = 1 / 0;
};
let w: []i32 = alloc([], 4u64)!;
w[2] = 5i32;
if (w[2] != 5) {
let _: i32 = 1 / 0;
};
};
// #87 PIN (rob §3, tagged kind): a module-GLOBAL tagged scrutinee must
// match to the SAME arm/value as a LOCAL of the same type — both stages.
// Pre-fix the global match SEGV'd (saved-BP-as-tag) on cs AND ww.
@test fn check_tagged_global_int() void = {
let l: (i32 | str) = 42; // the known-correct runtime box
let gv: i32 = match (g87_int) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
let lv: i32 = match (l) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
if (gv != 42) { let _: i32 = 1 / 0; };
if (gv != lv) { let _: i32 = 1 / 0; }; // global == local
};
// #87 PIN: the str variant (tag 1) exercises the DATAR ptr patch + the
// 24B {ptr,len,cap} payload at +8.
@test fn check_tagged_global_str() void = {
let n: i32 = match (g87_str) {
case let v: i32 => yield v;
case let s: str => yield s.len: i32;
};
if (n != 1) { let _: i32 = 1 / 0; }; // "x".len, via tag-1 arm
};
// #86 LOCK PIN (tuple kind): the already-correct tuple-global emitter
// must keep byte-mirroring a local — guards the working path against any
// regression from the #87 tagged arm sharing emitletdataw.
@test fn check_tuple_global_lock() void = {
let l: (u32, u32) = (1u32, 2u32);
if (g86_tup.0 != 1) { let _: i32 = 1 / 0; };
if (g86_tup.1 != 2) { let _: i32 = 1 / 0; };
if (g86_tup.0 != l.0) { let _: i32 = 1 / 0; };
if (g86_tup.1 != l.1) { let _: i32 = 1 / 0; };
};
// #27b: `<enum> as <inttype>` must lower as a value reinterpret (the
// integer storage already occupies the register), NOT a tagged-union
// assertion. Pre-fix wwstage gated the passthrough on NODE SHAPE
// (isenumexpr), so a constant-folded enum MEMBER (`eflag.B`, an int-
// literal node carrying the enum type_) missed the arm and fell into the
// tag-assert + exit(1) path — every fnmatch `(fl as i32)` flag test
// aborted (8/8 → 0/8 on the wwstage build). Fix gates on the stamped
// operand/result TYPE (mirror cstage cmd/w6c/cgen.c N_TYPEASSERT). These
// rows pin the construct by construction: member-as (the folded shape),
// ident-as (the param shape), the fnmatch flag-test combination, int→enum
// (result-type gate), and multiple enum bases × int widths.
@test fn check_enum_as_member() void = {
if ((eflag.B as i32) != 2) { let _: i32 = 1 / 0; };
if ((eflag.C as i32) != 4) { let _: i32 = 1 / 0; };
};
@test fn check_enum_as_flagtest() void = {
let fl: eflag = eflag.A; // 1 & 2 == 0
if (((fl as i32) & (eflag.B as i32)) != 0) { let _: i32 = 1 / 0; };
let fl2: eflag = eflag.C; // 4 & 4 != 0
if (((fl2 as i32) & (eflag.C as i32)) == 0) { let _: i32 = 1 / 0; };
};
@test fn check_enum_as_widths() void = {
if ((ebyte.HI as u8) != 200) { let _: i32 = 1 / 0; };
if ((ebyte.HI as i32) != 200) { let _: i32 = 1 / 0; };
if ((eplain.TWO as int) != 2) { let _: i32 = 1 / 0; };
if ((eflag.B as i64) != 2i64) { let _: i32 = 1 / 0; };
};
@test fn check_int_as_enum() void = {
let v: i32 = 2i32;
let e: eflag = v as eflag; // result-type enum gate
if ((e as i32) != 2) { let _: i32 = 1 / 0; };
};
// CONTROL (#27b): a legit tagged-union `as` must KEEP the tag-assert +
// payload-unwrap lowering — byte-id UNMOVED by the enum-passthrough fix.
// This is the path enum-as must not hijack and must not be hijacked by.
@test fn check_tagged_as_control() void = {
let b: (i32 | str) = 7;
let v: i32 = b as i32;
if (v != 7) { let _: i32 = 1 / 0; };
};