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)
230 lines
7.9 KiB
Plaintext
230 lines
7.9 KiB
Plaintext
// @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; };
|
||
};
|