diff --git a/Makefile b/Makefile index 9a25fb83..6d1bd546 100644 --- a/Makefile +++ b/Makefile @@ -353,8 +353,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_tryprop_tag_remap_run \ $(BIN)/test_nullable_try_run \ $(BIN)/test_nullable_assert_run \ - $(BIN)/test_idxfield_compound_run \ - $(BIN)/test_dotfield_compound_run \ $(BIN)/test_nested_union_widen_run \ $(BIN)/test_sret_struct_return \ $(BIN)/test_tagged_sret_run \ @@ -2146,18 +2144,6 @@ $(BIN)/test_nullable_assert_run: test/wcc/949_nullable_assert_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_idxfield_compound_run: test/wcc/949_idxfield_compound_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_dotfield_compound_run: test/wcc/949_dotfield_compound_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_sret_struct_return: test/wcc/721_sret_struct_return.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -3310,7 +3296,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(wildcard test/lang/*_test.ww) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 50 +LANGBYTEID_EXPECTED_MIN = 52 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/test/lang/dotfield_compound_test.ww b/test/lang/dotfield_compound_test.ww new file mode 100644 index 00000000..3e7385b4 --- /dev/null +++ b/test/lang/dotfield_compound_test.ww @@ -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); +}; diff --git a/test/lang/idxfield_compound_test.ww b/test/lang/idxfield_compound_test.ww new file mode 100644 index 00000000..6b131e4d --- /dev/null +++ b/test/lang/idxfield_compound_test.ww @@ -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); +}; diff --git a/test/wcc/949_dotfield_compound_run.c b/test/wcc/949_dotfield_compound_run.c deleted file mode 100644 index 849d4441..00000000 --- a/test/wcc/949_dotfield_compound_run.c +++ /dev/null @@ -1,390 +0,0 @@ -/* - * 949_dotfield_compound_run — runtime + byte-id net for #34 (#263 both- - * stages): 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 any op - * other than += / -= to a plain `s.f = rhs`. - * - * Pre-fix: every single-dot field-compound arm in BOTH stages wired only - * PLUSEQ/MINUSEQ; *= /= %= &= |= ^= <<= >>= left the old value in BX and - * the rhs in AX, then fell into the generic store of AX — `s.f *= 3` - * silently compiled to `s.f = 3` (gate-blind, .s byte-identical). The - * five sub-arms (via-ptr-base local, direct struct local, str/slice - * pseudo-field, global struct field) all shared the BX=old/AX=rhs - * convention; the fix routes every one through one shared combine helper - * (cgdotfieldcombine / cg_dotfield_combine) wiring all 10 ops and - * hard-errors float/str/slice/tagged fields LOUD (rule-7). cs/ww move - * together (#263 carve-out); byte-id witnesses rule-10. - * - * Each row carries (a) a cstage `ww build` + run asserting the exit code - * and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). builderr rows - * assert BOTH stages fail loud with the cited diagnostic substring. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -/* builderr + experr: when builderr != 0 the cstage build MUST FAIL and - * stderr MUST contain experr. wwstage builderr is verified via direct - * w6c_ww invocation on the same source. Mirrors 945_tuple_nary's pattern - * for loud-stop verification (rule 7 — never silent). */ -struct row { const char *label; const char *src; int want_exit; - int builderr; const char *experr; }; - -static const struct row rows[] = { - /* += control: the base op was already wired — byte-id surface - * unchanged. 10 + 5 = 15. */ - { "dotfld_pluseq_ctrl", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=10, g=0};\n" - " s.f += 5;\n" - " return s.f: i32;\n" - "};\n", 15, 0, NULL }, - /* #34 newly-wired: direct struct local *= : 5 * 3 = 15. */ - { "dotfld_stareq", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=5, g=0};\n" - " s.f *= 3;\n" - " return s.f: i32;\n" - "};\n", 15, 0, NULL }, - /* signed /= : 100 / 4 = 25 (CQO+IDIVQ). */ - { "dotfld_slasheq_signed", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=100, g=0};\n" - " s.f /= 4;\n" - " return s.f: i32;\n" - "};\n", 25, 0, NULL }, - /* signed /= with a NEGATIVE dividend distinguishes IDIVQ from DIVQ: - * -17 / 4 = -4 (CQO+IDIVQ, truncate toward zero); the positive signed - * /= above cannot tell IDIVQ from DIVQ. return (-4 + 50) = 46. */ - { "dotfld_slasheq_neg", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=-17, g=0};\n" - " s.f /= 4;\n" - " return s.f + 50;\n" - "};\n", 46, 0, NULL }, - /* unsigned u32 field /= : 100u32 / 4u32 = 25 (DIVQ + zero-DX). */ - { "dotfld_slasheq_unsigned", - "package main;\n" - "type S = struct { f: u32, g: u32 };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=100u32, g=0u32};\n" - " s.f /= 4u32;\n" - " return s.f: i32;\n" - "};\n", 25, 0, NULL }, - /* signed %= : 17 % 5 = 2 (result rides DX, MOVQ DX,AX). */ - { "dotfld_percenteq", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=17, g=0};\n" - " s.f %= 5;\n" - " return s.f: i32;\n" - "};\n", 2, 0, NULL }, - /* <<= : 3 << 4 = 48 (count swapped into CX, value into AX, SHLQ). */ - { "dotfld_lshifteq", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=3, g=0};\n" - " s.f <<= 4;\n" - " return s.f: i32;\n" - "};\n", 48, 0, NULL }, - /* signed >>= on a NEGATIVE i32 field: -16 >> 2 = -4 ARITHMETIC - * (SARQ). return (-4 + 50) = 46 distinguishes from a logical shift. */ - { "dotfld_rshifteq_neg", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=-16, g=0};\n" - " s.f >>= 2;\n" - " return s.f + 50;\n" - "};\n", 46, 0, NULL }, - /* unsigned u32 field >>= : 200u32 >> 2 = 50 (SHRQ). */ - { "dotfld_rshifteq_unsigned", - "package main;\n" - "type S = struct { f: u32, g: u32 };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=200u32, g=0u32};\n" - " s.f >>= 2u32;\n" - " return s.f: i32;\n" - "};\n", 50, 0, NULL }, - /* via-ptr base (`p.f` where p is *S fn param): the arm derefs the - * pointer before the field load/store. 4 * 3 = 12. */ - { "dotfld_viaptr_stareq", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "fn bump(p: *S) void = { p.f *= 3; };\n" - "export fn main() i32 = {\n" - " let a: S = S{f=4, g=0};\n" - " bump(&a);\n" - " return a.f: i32;\n" - "};\n", 12, 0, NULL }, - /* global struct field: gs.f /= 4 → 5 (LEAQ gs(SB) base). */ - { "dotfld_global_slasheq", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "let gs: S = S{f=20, g=0};\n" - "export fn main() i32 = {\n" - " gs.f /= 4;\n" - " return gs.f: i32;\n" - "};\n", 5, 0, NULL }, - /* str/slice pseudo-field compound (`sl.len -= 3`): the pseudo-field - * arm (delta = 8 for .len) must combine, not store the rhs. 8-3 = 5. */ - { "dotfld_pseudo_len_minuseq", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8];\n" - " let sl: []u8 = buf[0:8];\n" - " sl.len -= 3;\n" - " return sl.len: i32;\n" - "};\n", 5, 0, NULL }, - /* pseudo-field *= (the silently-demoted op): 4 * 3 = 12. */ - { "dotfld_pseudo_len_stareq", - "package main;\n" - "export fn main() i32 = {\n" - " let buf: [8]u8 = [1u8,2u8,3u8,4u8,5u8,6u8,7u8,8u8];\n" - " let sl: []u8 = buf[0:4];\n" - " sl.len *= 3;\n" - " return sl.len: i32;\n" - "};\n", 12, 0, NULL }, - /* Plain `s.f = v` control: ASSIGN path byte-id unchanged. */ - { "dotfld_plain_assign_ctrl", - "package main;\n" - "type S = struct { f: int, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=1, g=0};\n" - " s.f = 99;\n" - " return s.f: i32;\n" - "};\n", 99, 0, NULL }, - /* HARD-ERROR rows (#34/rule-7): float/str/slice/tagged FIELD compound - * builds MUST FAIL LOUD on both stages with the cited diagnostic. */ - { "dotfld_he_float", - "package main;\n" - "type S = struct { f: f64, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=1.0, g=0};\n" - " s.f *= 2.0;\n" - " return 0;\n" - "};\n", 0, 1, "single-dot field compound on float field" }, - { "dotfld_he_str", - "package main;\n" - "type S = struct { f: str, g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=\"a\", g=0};\n" - " s.f += \"x\";\n" - " return 0;\n" - "};\n", 0, 1, "single-dot field compound on str field" }, - { "dotfld_he_slice", - "package main;\n" - "type S = struct { f: []u8, g: int };\n" - "export fn main() i32 = {\n" - " let b: [2]u8 = [1u8, 2u8];\n" - " let s: S = S{f=b[0:2], g=0};\n" - " s.f += b[0:1];\n" - " return 0;\n" - "};\n", 0, 1, "single-dot field compound on slice field" }, - { "dotfld_he_tagged", - "package main;\n" - "type S = struct { f: (i32|str), g: int };\n" - "export fn main() i32 = {\n" - " let s: S = S{f=1, g=0};\n" - " s.f += 3;\n" - " return 0;\n" - "};\n", 0, 1, "single-dot field compound on tagged field" }, - { NULL, NULL, 0, 0, NULL } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "idxcompound: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwidx_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run, OR build-must-fail (builderr rows). - * For builderr: stderr captured to a temp file; pass iff - * exit-nonzero AND stderr contains experr substring. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwidx_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char cmd[2048]; - if (rows[i].builderr) { - char errf[96]; - snprintf(errf, sizeof errf, "/tmp/wwidx_%d_e_%d", - getpid(), i); - snprintf(cmd, sizeof cmd, - "cd %s && %s/ww build %s >/dev/null 2>%s", - tmpdir, bin, src, errf); - int brc = runwait(cmd); - FILE *ef = fopen(errf, "rb"); - char ebuf[4096]; - size_t en = 0; - if (ef) { en = fread(ebuf, 1, sizeof ebuf - 1, ef); - fclose(ef); } - ebuf[en] = '\0'; - int ok = (brc != 0) - && (rows[i].experr == NULL - || strstr(ebuf, rows[i].experr) != NULL); - if (!ok) { - fprintf(stderr, "row[%s]: cstage expected " - "builderr+'%s' (brc=%d, stderr='%s')\n", - rows[i].label, - rows[i].experr ? rows[i].experr : "(any)", - brc, ebuf); - fail++; - } - - /* Also verify wwstage hard-errors with the SAME message - * (rule-10 — both stages identical diagnostic). Invoke - * w6c_ww directly on the .ww source. */ - snprintf(cmd, sizeof cmd, - "%s -o /dev/null %s >/dev/null 2>%s", - w6c_ww, src, errf); - int wrc = runwait(cmd); - ef = fopen(errf, "rb"); en = 0; - if (ef) { en = fread(ebuf, 1, sizeof ebuf - 1, ef); - fclose(ef); } - ebuf[en] = '\0'; - int wok = (wrc != 0) - && (rows[i].experr == NULL - || strstr(ebuf, rows[i].experr) != NULL); - if (!wok) { - fprintf(stderr, "row[%s]: wwstage expected " - "builderr+'%s' (wrc=%d, stderr='%s')\n", - rows[i].label, - rows[i].experr ? rows[i].experr : "(any)", - wrc, ebuf); - fail++; - } - unlink(errf); - unlink(src); rmdir(tmpdir); - continue; - } - - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - unlink(src); rmdir(tmpdir); - continue; - } - - char outbin[128]; - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - unlink(outbin); rmdir(tmpdir); - - /* (b) cs==ww byte-id gate. */ - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwidx_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwidx_%d_%d_ww.s", - getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; unlink(src); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d dotfield-compound tests failed\n", - fail, n); - return 1; - } - printf("dotfield-compound: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/949_idxfield_compound_run.c b/test/wcc/949_idxfield_compound_run.c deleted file mode 100644 index 68f9f090..00000000 --- a/test/wcc/949_idxfield_compound_run.c +++ /dev/null @@ -1,373 +0,0 @@ -/* - * 949_idxfield_compound_run — runtime + byte-id net for #33 (#263 both- - * stages): 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. - * - * Pre-fix: the arr[i].field compound arm (cgenexpr.ww + cgen.c) wired only - * PLUSEQ/MINUSEQ/STAREQ/AMPEQ/PIPEEQ/CARETEQ; for SLASHEQ/PERCENTEQ/ - * LSHIFTEQ/RSHIFTEQ the old field value loaded into AX, no combine fired, - * and AX was stored back — a silent no-op in BOTH stages (gate-blind, - * .s byte-identical). float/str/slice/tagged field compound on this arm - * was also unguarded (silent fall-through). The fix wires all 10 ops - * (SLASHEQ/PERCENTEQ via CQO+IDIVQ signed / zero-DX+DIVQ unsigned; - * LSHIFTEQ via SHLQ; RSHIFTEQ via SARQ signed / SHRQ unsigned) and - * hard-errors the 4 unwired payload kinds LOUD (rule-7) — mirroring the - * #133 indexed-scalar + chained-ptr-field arms. cs/ww move together - * (#263 carve-out); byte-id witnesses rule-10. - * - * Each row carries (a) a cstage `ww build` + run asserting the exit code - * and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). builderr rows assert - * BOTH stages fail loud with the cited diagnostic substring. - */ -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -/* builderr + experr: when builderr != 0 the cstage build MUST FAIL and - * stderr MUST contain experr. wwstage builderr is verified via direct - * w6c_ww invocation on the same source. Mirrors 945_tuple_nary's pattern - * for loud-stop verification (rule 7 — never silent). */ -struct row { const char *label; const char *src; int want_exit; - int builderr; const char *experr; }; - -static const struct row rows[] = { - /* The 6 base ops were already wired; control row that the - * load-op-store surface is unchanged. 100 + 50 = 150. */ - { "fld_i32_pluseq", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=100, g=0}, S{f=0, g=0}];\n" - " xs[0].f += 50;\n" - " return xs[0].f;\n" - "};\n", 150, 0, NULL }, - /* i64 field *= : full-width. 6 * 7 = 42. */ - { "fld_i64_stareq", - "package main;\n" - "type S = struct { f: i64, g: i64 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=6i64, g=0i64}, S{f=0i64, g=0i64}];\n" - " xs[0].f *= 7i64;\n" - " return xs[0].f: i32;\n" - "};\n", 42, 0, NULL }, - /* #33 newly-wired: signed i32 /= : 100 / 4 = 25. */ - { "fld_i32_slasheq", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=100, g=0}, S{f=0, g=0}];\n" - " xs[0].f /= 4;\n" - " return xs[0].f;\n" - "};\n", 25, 0, NULL }, - /* signed /= with a NEGATIVE dividend: -17 / 4 = -4 ARITHMETIC - * (CQO+IDIVQ truncates toward zero); an unsigned DIVQ on the - * sign-extended -17 yields garbage. This is the row the positive - * signed /= above cannot tell apart from DIVQ. return (-4+50)=46. */ - { "fld_i32_slasheq_neg", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=-17, g=0}, S{f=0, g=0}];\n" - " xs[0].f /= 4;\n" - " return xs[0].f + 50;\n" - "};\n", 46, 0, NULL }, - /* unsigned u32 /= : 100u32 / 4u32 = 25 (DIVQ + zero-DX, not IDIVQ). */ - { "fld_u32_slasheq", - "package main;\n" - "type S = struct { f: u32, g: u32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=100u32, g=0u32}, S{f=0u32, g=0u32}];\n" - " xs[0].f /= 4u32;\n" - " return xs[0].f: i32;\n" - "};\n", 25, 0, NULL }, - /* signed i32 %= : 17 % 5 = 2 (result rides DX, MOVQ DX,AX). */ - { "fld_i32_percenteq", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=17, g=0}, S{f=0, g=0}];\n" - " xs[0].f %= 5;\n" - " return xs[0].f;\n" - "};\n", 2, 0, NULL }, - /* unsigned u32 %= : 100u32 % 7u32 = 2. */ - { "fld_u32_percenteq", - "package main;\n" - "type S = struct { f: u32, g: u32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=100u32, g=0u32}, S{f=0u32, g=0u32}];\n" - " xs[0].f %= 7u32;\n" - " return xs[0].f: i32;\n" - "};\n", 2, 0, NULL }, - /* i32 <<= : 3 << 4 = 48 (SHLQ via CX). */ - { "fld_i32_lshifteq", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=3, g=0}, S{f=0, g=0}];\n" - " xs[0].f <<= 4;\n" - " return xs[0].f;\n" - "};\n", 48, 0, NULL }, - /* unsigned u32 >>= : 200u32 >> 2 = 50 (SHRQ). */ - { "fld_u32_rshifteq", - "package main;\n" - "type S = struct { f: u32, g: u32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=200u32, g=0u32}, S{f=0u32, g=0u32}];\n" - " xs[0].f >>= 2u32;\n" - " return xs[0].f: i32;\n" - "};\n", 50, 0, NULL }, - /* signed i32 >>= on a NEGATIVE value: -16 >> 2 = -4 ARITHMETIC - * (SARQ, not SHRQ — a logical shift would give a huge positive). - * return (-4 + 50) = 46 distinguishes the two. */ - { "fld_i32_rshifteq_neg", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=-16, g=0}, S{f=0, g=0}];\n" - " xs[0].f >>= 2;\n" - " return xs[0].f + 50;\n" - "};\n", 46, 0, NULL }, - /* via-ptr element (`[N]*S`): the arm derefs once before the field - * load/store. 10 + 5 = 15. */ - { "fld_viaptr_pluseq", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let a = S{f=10, g=0};\n" - " let xs: [2]*S = [&a, &a];\n" - " xs[0].f += 5;\n" - " return a.f;\n" - "};\n", 15, 0, NULL }, - /* Plain `arr[i].field = v` control: ASSIGN path byte-id unchanged. */ - { "fld_plain_assign_ctrl", - "package main;\n" - "type S = struct { f: i32, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=1, g=0}, S{f=0, g=0}];\n" - " xs[0].f = 99;\n" - " return xs[0].f;\n" - "};\n", 99, 0, NULL }, - /* HARD-ERROR rows (#33/rule-7): float/str/slice/tagged FIELD compound - * builds MUST FAIL LOUD on both stages with the cited diagnostic. */ - { "fld_he_float", - "package main;\n" - "type S = struct { f: f64, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=1.0, g=0}, S{f=0.0, g=0}];\n" - " xs[0].f /= 2.0;\n" - " return 0;\n" - "};\n", 0, 1, "arr[i].field compound on float field" }, - { "fld_he_str", - "package main;\n" - "type S = struct { f: str, g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=\"a\", g=0}, S{f=\"b\", g=0}];\n" - " xs[0].f += \"x\";\n" - " return 0;\n" - "};\n", 0, 1, "arr[i].field compound on str field" }, - { "fld_he_slice", - "package main;\n" - "type S = struct { f: []u8, g: i32 };\n" - "export fn main() i32 = {\n" - " let b: [2]u8 = [1u8, 2u8];\n" - " let xs: [2]S = [S{f=b[0:2], g=0}, S{f=b[0:2], g=0}];\n" - " xs[0].f += b[0:1];\n" - " return 0;\n" - "};\n", 0, 1, "arr[i].field compound on slice field" }, - { "fld_he_tagged", - "package main;\n" - "type S = struct { f: (i32|str), g: i32 };\n" - "export fn main() i32 = {\n" - " let xs: [2]S = [S{f=1, g=0}, S{f=2, g=0}];\n" - " xs[0].f += 3;\n" - " return 0;\n" - "};\n", 0, 1, "arr[i].field compound on tagged field" }, - { NULL, NULL, 0, 0, NULL } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "idxcompound: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char src[64]; - snprintf(src, sizeof src, "/tmp/wwidx_%d_%d.ww", getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run, OR build-must-fail (builderr rows). - * For builderr: stderr captured to a temp file; pass iff - * exit-nonzero AND stderr contains experr substring. */ - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwidx_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char cmd[2048]; - if (rows[i].builderr) { - char errf[96]; - snprintf(errf, sizeof errf, "/tmp/wwidx_%d_e_%d", - getpid(), i); - snprintf(cmd, sizeof cmd, - "cd %s && %s/ww build %s >/dev/null 2>%s", - tmpdir, bin, src, errf); - int brc = runwait(cmd); - FILE *ef = fopen(errf, "rb"); - char ebuf[4096]; - size_t en = 0; - if (ef) { en = fread(ebuf, 1, sizeof ebuf - 1, ef); - fclose(ef); } - ebuf[en] = '\0'; - int ok = (brc != 0) - && (rows[i].experr == NULL - || strstr(ebuf, rows[i].experr) != NULL); - if (!ok) { - fprintf(stderr, "row[%s]: cstage expected " - "builderr+'%s' (brc=%d, stderr='%s')\n", - rows[i].label, - rows[i].experr ? rows[i].experr : "(any)", - brc, ebuf); - fail++; - } - - /* Also verify wwstage hard-errors with the SAME message - * (rule-10 — both stages identical diagnostic). Invoke - * w6c_ww directly on the .ww source. */ - snprintf(cmd, sizeof cmd, - "%s -o /dev/null %s >/dev/null 2>%s", - w6c_ww, src, errf); - int wrc = runwait(cmd); - ef = fopen(errf, "rb"); en = 0; - if (ef) { en = fread(ebuf, 1, sizeof ebuf - 1, ef); - fclose(ef); } - ebuf[en] = '\0'; - int wok = (wrc != 0) - && (rows[i].experr == NULL - || strstr(ebuf, rows[i].experr) != NULL); - if (!wok) { - fprintf(stderr, "row[%s]: wwstage expected " - "builderr+'%s' (wrc=%d, stderr='%s')\n", - rows[i].label, - rows[i].experr ? rows[i].experr : "(any)", - wrc, ebuf); - fail++; - } - unlink(errf); - unlink(src); rmdir(tmpdir); - continue; - } - - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - unlink(src); rmdir(tmpdir); - continue; - } - - char outbin[128]; - const char *base = strrchr(src, '/'); - base = base ? base + 1 : src; - snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); - char *dot = strrchr(outbin, '.'); - if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - unlink(outbin); rmdir(tmpdir); - - /* (b) cs==ww byte-id gate. */ - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwidx_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwidx_%d_%d_ww.s", - getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; unlink(src); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; unlink(src); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - unlink(src); unlink(cs_s); unlink(ws_s); - } - - if (fail) { - fprintf(stderr, "%d/%d idxfield-compound tests failed\n", - fail, n); - return 1; - } - printf("idxfield-compound: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/data/dotfield_compound_float/case.ww b/test/wcc/data/dotfield_compound_float/case.ww new file mode 100644 index 00000000..ec6ac20b --- /dev/null +++ b/test/wcc/data/dotfield_compound_float/case.ww @@ -0,0 +1,14 @@ +//ww:error "single-dot field compound on float field" +// Lifted from test/wcc/949_dotfield_compound_run.c reject row "dotfld_he_float" +// (#34/rule-7): a compound assign on a single-dot FLOAT field is unwired in +// both stages and MUST loud-fail, not silently demote to a plain store. The +// substring is the shared diagnostic body — byte-identical on both stages, +// pinned to "float field" so it cannot also match the str/slice/tagged rows +// (no file:line, no cstage "ww: " prefix — both differ cs vs ww). +package main; +type S = struct { f: f64, g: int }; +export fn main() i32 = { + let s: S = S{f=1.0, g=0}; + s.f *= 2.0; + return 0; +}; diff --git a/test/wcc/data/dotfield_compound_slice/case.ww b/test/wcc/data/dotfield_compound_slice/case.ww new file mode 100644 index 00000000..83c20396 --- /dev/null +++ b/test/wcc/data/dotfield_compound_slice/case.ww @@ -0,0 +1,15 @@ +//ww:error "single-dot field compound on slice field" +// Lifted from test/wcc/949_dotfield_compound_run.c reject row "dotfld_he_slice" +// (#34/rule-7): a compound assign on a single-dot SLICE field is unwired in +// both stages and MUST loud-fail. The substring is the shared diagnostic body +// — byte-identical on both stages, pinned to "slice field" so it cannot also +// match the float/str/tagged rows (no file:line, no cstage "ww: " prefix — +// both differ cs vs ww). +package main; +type S = struct { f: []u8, g: int }; +export fn main() i32 = { + let b: [2]u8 = [1u8, 2u8]; + let s: S = S{f=b[0:2], g=0}; + s.f += b[0:1]; + return 0; +}; diff --git a/test/wcc/data/dotfield_compound_str/case.ww b/test/wcc/data/dotfield_compound_str/case.ww new file mode 100644 index 00000000..dd1e255e --- /dev/null +++ b/test/wcc/data/dotfield_compound_str/case.ww @@ -0,0 +1,14 @@ +//ww:error "single-dot field compound on str field" +// Lifted from test/wcc/949_dotfield_compound_run.c reject row "dotfld_he_str" +// (#34/rule-7): a compound assign on a single-dot STR field is unwired in both +// stages and MUST loud-fail. The substring is the shared diagnostic body — +// byte-identical on both stages, pinned to "str field" so it cannot also match +// the float/slice/tagged rows (no file:line, no cstage "ww: " prefix — both +// differ cs vs ww). +package main; +type S = struct { f: str, g: int }; +export fn main() i32 = { + let s: S = S{f="a", g=0}; + s.f += "x"; + return 0; +}; diff --git a/test/wcc/data/dotfield_compound_tagged/case.ww b/test/wcc/data/dotfield_compound_tagged/case.ww new file mode 100644 index 00000000..a9fcfc2a --- /dev/null +++ b/test/wcc/data/dotfield_compound_tagged/case.ww @@ -0,0 +1,14 @@ +//ww:error "single-dot field compound on tagged field" +// Lifted from test/wcc/949_dotfield_compound_run.c reject row "dotfld_he_tagged" +// (#34/rule-7): a compound assign on a single-dot TAGGED-union field is unwired +// in both stages and MUST loud-fail. The substring is the shared diagnostic +// body — byte-identical on both stages, pinned to "tagged field" so it cannot +// also match the float/str/slice rows (no file:line, no cstage "ww: " prefix — +// both differ cs vs ww). +package main; +type S = struct { f: (i32|str), g: int }; +export fn main() i32 = { + let s: S = S{f=1, g=0}; + s.f += 3; + return 0; +}; diff --git a/test/wcc/data/idxfield_compound_float/case.ww b/test/wcc/data/idxfield_compound_float/case.ww new file mode 100644 index 00000000..f2ac8305 --- /dev/null +++ b/test/wcc/data/idxfield_compound_float/case.ww @@ -0,0 +1,14 @@ +//ww:error "arr[i].field compound on float field" +// Lifted from test/wcc/949_idxfield_compound_run.c reject row "fld_he_float" +// (#33/rule-7): a compound assign on an indexed-element FLOAT field is unwired +// in both stages and MUST loud-fail, not silently fall through. The substring +// is the shared diagnostic body — byte-identical on both stages, pinned to +// "float field" so it cannot also match the str/slice/tagged rows (no +// file:line, no cstage "ww: " prefix — both differ cs vs ww). +package main; +type S = struct { f: f64, g: i32 }; +export fn main() i32 = { + let xs: [2]S = [S{f=1.0, g=0}, S{f=0.0, g=0}]; + xs[0].f /= 2.0; + return 0; +}; diff --git a/test/wcc/data/idxfield_compound_slice/case.ww b/test/wcc/data/idxfield_compound_slice/case.ww new file mode 100644 index 00000000..7654b61f --- /dev/null +++ b/test/wcc/data/idxfield_compound_slice/case.ww @@ -0,0 +1,15 @@ +//ww:error "arr[i].field compound on slice field" +// Lifted from test/wcc/949_idxfield_compound_run.c reject row "fld_he_slice" +// (#33/rule-7): a compound assign on an indexed-element SLICE field is unwired +// in both stages and MUST loud-fail. The substring is the shared diagnostic +// body — byte-identical on both stages, pinned to "slice field" so it cannot +// also match the float/str/tagged rows (no file:line, no cstage "ww: " prefix +// — both differ cs vs ww). +package main; +type S = struct { f: []u8, g: i32 }; +export fn main() i32 = { + let b: [2]u8 = [1u8, 2u8]; + let xs: [2]S = [S{f=b[0:2], g=0}, S{f=b[0:2], g=0}]; + xs[0].f += b[0:1]; + return 0; +}; diff --git a/test/wcc/data/idxfield_compound_str/case.ww b/test/wcc/data/idxfield_compound_str/case.ww new file mode 100644 index 00000000..edcfed34 --- /dev/null +++ b/test/wcc/data/idxfield_compound_str/case.ww @@ -0,0 +1,14 @@ +//ww:error "arr[i].field compound on str field" +// Lifted from test/wcc/949_idxfield_compound_run.c reject row "fld_he_str" +// (#33/rule-7): a compound assign on an indexed-element STR field is unwired in +// both stages and MUST loud-fail. The substring is the shared diagnostic body +// — byte-identical on both stages, pinned to "str field" so it cannot also +// match the float/slice/tagged rows (no file:line, no cstage "ww: " prefix — +// both differ cs vs ww). +package main; +type S = struct { f: str, g: i32 }; +export fn main() i32 = { + let xs: [2]S = [S{f="a", g=0}, S{f="b", g=0}]; + xs[0].f += "x"; + return 0; +}; diff --git a/test/wcc/data/idxfield_compound_tagged/case.ww b/test/wcc/data/idxfield_compound_tagged/case.ww new file mode 100644 index 00000000..deacb0b5 --- /dev/null +++ b/test/wcc/data/idxfield_compound_tagged/case.ww @@ -0,0 +1,14 @@ +//ww:error "arr[i].field compound on tagged field" +// Lifted from test/wcc/949_idxfield_compound_run.c reject row "fld_he_tagged" +// (#33/rule-7): a compound assign on an indexed-element TAGGED-union field is +// unwired in both stages and MUST loud-fail. The substring is the shared +// diagnostic body — byte-identical on both stages, pinned to "tagged field" so +// it cannot also match the float/str/slice rows (no file:line, no cstage +// "ww: " prefix — both differ cs vs ww). +package main; +type S = struct { f: (i32|str), g: i32 }; +export fn main() i32 = { + let xs: [2]S = [S{f=1, g=0}, S{f=2, g=0}]; + xs[0].f += 3; + return 0; +};