test: migrate dotfield/idxfield compound-assign to @test + runww rejects, retire C twins (fold-3)

949_dotfield_compound + 949_idxfield_compound are one bug class (#133-lineage compound-assign load-op-store on field lvalues; #34/#33, #263 carve-out) sharing the combine + hard-error path, so the two C carriers fuse into one commit: 26 value rows -> test/lang @test row-tables (primitive-only asserts), 8 reject rows -> runww //ww:error dual-stage carriers. byteid floor 50->52.
This commit is contained in:
2026-06-22 16:34:33 +09:00
parent af3c49a4c7
commit 057e805cf0
13 changed files with 354 additions and 778 deletions

View File

@@ -0,0 +1,123 @@
// dotfield_compound_test — single-dot field compound-assign (#34, #263 both
// stages), migrated from test/wcc/949_dotfield_compound_run.c. A compound
// assign on a single-dot field lvalue (`s.f OP= v`, `p.f OP= v`, `g.f OP= v`,
// `sl.len OP= v`) must do a real LOAD-OP-STORE for ALL ten integer ops, not
// silently demote a non-+=/-= op to a plain `s.f = rhs` (the prior bug: *= /=
// %= &= |= ^= <<= >>= left old-in-BX/rhs-in-AX then stored AX, so `s.f *= 3`
// compiled to `s.f = 3`, gate-blind/byte-identical). Every row init-poisons
// the field with a value != the expected result, runs the op, reads the field
// back, and asserts the sibling field `g` (or the slice `.cap`) is UNTOUCHED to
// catch an over-wide / wrong-offset store. PRIMITIVE-only asserts (no
// fmt/strconv) so a co-miscompile in the assert path cannot mask the bug. The
// matrix covers + * / % << >> across int/i32/u32 fields, a via-ptr base, a
// global struct field, and the str/slice `.len` pseudo-field; the plain-assign
// control pins the ASSIGN path the fix must leave unchanged. T2 keeps cs==ww.
package dotfield_compound_test;
type Sii = struct { f: int, g: int };
type S32 = struct { f: i32, g: i32 };
type Su32 = struct { f: u32, g: u32 };
let gs: Sii = Sii{f=20, g=0};
fn bump(p: *Sii) void = { p.f *= 3; };
@test fn dotfld_pluseq_ctrl() void = {
let s: Sii = Sii{f=10, g=0};
s.f += 5;
assert(s.f == 15);
assert(s.g == 0);
};
@test fn dotfld_stareq() void = {
let s: Sii = Sii{f=5, g=0};
s.f *= 3;
assert(s.f == 15);
assert(s.g == 0);
};
@test fn dotfld_slasheq_signed() void = {
let s: Sii = Sii{f=100, g=0};
s.f /= 4;
assert(s.f == 25);
assert(s.g == 0);
};
@test fn dotfld_slasheq_neg() void = {
let s: S32 = S32{f=-17, g=0};
s.f /= 4;
assert(s.f == -4);
assert(s.g == 0);
};
@test fn dotfld_slasheq_unsigned() void = {
let s: Su32 = Su32{f=100u32, g=0u32};
s.f /= 4u32;
assert(s.f == 25u32);
assert(s.g == 0u32);
};
@test fn dotfld_percenteq() void = {
let s: Sii = Sii{f=17, g=0};
s.f %= 5;
assert(s.f == 2);
assert(s.g == 0);
};
@test fn dotfld_lshifteq() void = {
let s: Sii = Sii{f=3, g=0};
s.f <<= 4;
assert(s.f == 48);
assert(s.g == 0);
};
@test fn dotfld_rshifteq_neg() void = {
let s: S32 = S32{f=-16, g=0};
s.f >>= 2;
assert(s.f == -4);
assert(s.g == 0);
};
@test fn dotfld_rshifteq_unsigned() void = {
let s: Su32 = Su32{f=200u32, g=0u32};
s.f >>= 2u32;
assert(s.f == 50u32);
assert(s.g == 0u32);
};
@test fn dotfld_viaptr_stareq() void = {
let a: Sii = Sii{f=4, g=0};
bump(&a);
assert(a.f == 12);
assert(a.g == 0);
};
@test fn dotfld_global_slasheq() void = {
gs.f /= 4;
assert(gs.f == 5);
assert(gs.g == 0);
};
@test fn dotfld_pseudo_len_minuseq() void = {
let buf: [8]u8 = [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8];
let sl: []u8 = buf[0:8];
sl.len -= 3;
assert(sl.len == 5);
assert(sl.cap == 8);
};
@test fn dotfld_pseudo_len_stareq() void = {
let buf: [8]u8 = [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8];
let sl: []u8 = buf[0:4];
sl.len *= 3;
assert(sl.len == 12);
assert(sl.cap == 8);
};
@test fn dotfld_plain_assign_ctrl() void = {
let s: Sii = Sii{f=1, g=0};
s.f = 99;
assert(s.f == 99);
assert(s.g == 0);
};

View File

@@ -0,0 +1,116 @@
// idxfield_compound_test — indexed-element field compound-assign (#33, #263
// both stages), migrated from test/wcc/949_idxfield_compound_run.c. A compound
// assign on an indexed-element FIELD lvalue (`arr[i].field OP= v`) must do a
// real LOAD-OP-STORE for ALL ten integer ops, not silently no-op the four the
// arm never wired (SLASHEQ/PERCENTEQ/LSHIFTEQ/RSHIFTEQ loaded the old value
// into AX, fired no combine, and stored AX back — a silent no-op in BOTH
// stages, gate-blind/byte-identical). Every row init-poisons xs[0].f with a
// value != the expected result, runs the op, reads the field back, and asserts
// a sibling slot (xs[0].g or xs[1].f) is UNTOUCHED to catch an over-wide /
// wrong-offset store. PRIMITIVE-only asserts (no fmt/strconv) so a co-
// miscompile in the assert path cannot mask the bug. The matrix covers
// + * / % << >> across i32/i64/u32 fields and a via-ptr element ([N]*S); the
// plain-assign control pins the ASSIGN path the fix must leave unchanged. T2
// keeps cs==ww.
package idxfield_compound_test;
type S32 = struct { f: i32, g: i32 };
type S64 = struct { f: i64, g: i64 };
type Su32 = struct { f: u32, g: u32 };
@test fn fld_i32_pluseq() void = {
let xs: [2]S32 = [S32{f=100, g=0}, S32{f=0, g=0}];
xs[0].f += 50;
assert(xs[0].f == 150);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_i64_stareq() void = {
let xs: [2]S64 = [S64{f=6i64, g=0i64}, S64{f=0i64, g=0i64}];
xs[0].f *= 7i64;
assert(xs[0].f == 42i64);
assert(xs[0].g == 0i64);
assert(xs[1].f == 0i64);
};
@test fn fld_i32_slasheq() void = {
let xs: [2]S32 = [S32{f=100, g=0}, S32{f=0, g=0}];
xs[0].f /= 4;
assert(xs[0].f == 25);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_i32_slasheq_neg() void = {
let xs: [2]S32 = [S32{f=-17, g=0}, S32{f=0, g=0}];
xs[0].f /= 4;
assert(xs[0].f == -4);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_u32_slasheq() void = {
let xs: [2]Su32 = [Su32{f=100u32, g=0u32}, Su32{f=0u32, g=0u32}];
xs[0].f /= 4u32;
assert(xs[0].f == 25u32);
assert(xs[0].g == 0u32);
assert(xs[1].f == 0u32);
};
@test fn fld_i32_percenteq() void = {
let xs: [2]S32 = [S32{f=17, g=0}, S32{f=0, g=0}];
xs[0].f %= 5;
assert(xs[0].f == 2);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_u32_percenteq() void = {
let xs: [2]Su32 = [Su32{f=100u32, g=0u32}, Su32{f=0u32, g=0u32}];
xs[0].f %= 7u32;
assert(xs[0].f == 2u32);
assert(xs[0].g == 0u32);
assert(xs[1].f == 0u32);
};
@test fn fld_i32_lshifteq() void = {
let xs: [2]S32 = [S32{f=3, g=0}, S32{f=0, g=0}];
xs[0].f <<= 4;
assert(xs[0].f == 48);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_u32_rshifteq() void = {
let xs: [2]Su32 = [Su32{f=200u32, g=0u32}, Su32{f=0u32, g=0u32}];
xs[0].f >>= 2u32;
assert(xs[0].f == 50u32);
assert(xs[0].g == 0u32);
assert(xs[1].f == 0u32);
};
@test fn fld_i32_rshifteq_neg() void = {
let xs: [2]S32 = [S32{f=-16, g=0}, S32{f=0, g=0}];
xs[0].f >>= 2;
assert(xs[0].f == -4);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};
@test fn fld_viaptr_pluseq() void = {
let a: S32 = S32{f=10, g=0};
let xs: [2]*S32 = [&a, &a];
xs[0].f += 5;
assert(a.f == 15);
assert(a.g == 0);
};
@test fn fld_plain_assign_ctrl() void = {
let xs: [2]S32 = [S32{f=1, g=0}, S32{f=0, g=0}];
xs[0].f = 99;
assert(xs[0].f == 99);
assert(xs[0].g == 0);
assert(xs[1].f == 0);
};