test: migrate Fam13 misc checker/coercion value tests to @test (#5-C6)

Final fold-2 chunk. The 12 Fam13 single-file value drivers move from
test/wcc/*_run.c into in-language @test row-tables under test/lang/:

- value rows -> test/lang/*_test.ww (12 files)
- reject rows -> runww //ww:error carriers (13, dual-stage non-vacuous)
- nullable abort rows -> runww //ww:run-exit 1 carriers (3)
- 953_globalslice_arg -> _runonly (cs!=ww checker divergence, #28)
- 788 value_not_type_neg + 953_arrlit_slice reject_assign kept as slim
  rc-only .c pins (divergent-diag dual-reject, mutation-gated); 788 #29

Coverage parity verified row-by-row vs each retired driver; advisor-
ratified carve taxonomy; two-round reviewed. Floor ratchet follows.
This commit is contained in:
2026-06-24 22:59:32 +09:00
parent 246e5bb90e
commit 132ea4ee60
41 changed files with 950 additions and 3160 deletions

View File

@@ -0,0 +1,52 @@
// append_structlit_evalorder_test — #59: an append/insert of a STRUCT-LITERAL
// value evaluates the value BEFORE the len-bump (Hare order). Migrated from
// test/wcc/946_append_structlit_evalorder_run.c (value rows; cs==ww byte-id
// rides T2).
//
// #263 both-wrong-IDENTICAL: pre-fix both stages filled the literal AFTER
// cg_append_grow bumped xs.len, so a `len(xs)` field expression saw the
// post-grow length (sum 6 not 3). The asm-diff/byte-id gates are blind to it —
// only a runtime readback is the net. insert() desugars to this same arm.
// narrow_neighbor (sub-8B struct filled to capacity) catches the 8B-over-copy
// regression: a wrong tail copy clobbers the adjacent `victim` allocation.
package append_structlit_evalorder_test;
type rec = struct { f: i64 };
type narrowrec = struct { a: i32 };
@test fn append_eval() void = {
let xs: []rec = [];
append(xs, rec{ f = len(xs): i64 });
append(xs, rec{ f = len(xs): i64 });
append(xs, rec{ f = len(xs): i64 });
let s: i64 = xs[0].f + xs[1].f + xs[2].f;
assert(s == 3);
};
@test fn insert_eval() void = {
let xs: []rec = [];
append(xs, rec{ f = 100 });
append(xs, rec{ f = 200 });
insert(xs[1], rec{ f = len(xs): i64 });
assert(xs[1].f == 2);
assert(xs[0].f == 100);
assert(xs[2].f == 200);
};
@test fn narrow_neighbor() void = {
let xs: []narrowrec = [];
append(xs, narrowrec{ a = len(xs): i32 });
let victim: []i64 = [];
append(victim, 1234605616436508552i64);
let i: i32 = 1;
for (i < 8) {
append(xs, narrowrec{ a = len(xs): i32 });
i += 1;
};
let s: i32 = 0;
let j: i32 = 0;
for (j < 8) { s += xs[j].a; j += 1; };
assert(s == 28);
assert(victim[0] == 1234605616436508552i64);
};

View File

@@ -0,0 +1,80 @@
// arraytoslice_test — #258: the implicit [N]T -> []T array-to-slice BORROW at
// assign / return / call-arg / let init (.ptr = &arr[0], .len = .cap = N).
// Migrated from test/wcc/953_arraytoslice_run.c (value rows; cs==ww byte-id
// rides T2). The element-type-mismatch reject rows stay as runww //ww:error
// carriers under test/wcc/data/.
//
// The fix is a checker-only desugar to the explicit full slice arr[0:len(arr)];
// cgen is untouched. Each row asserts through .ptr-deref (indexing) AND .cap,
// not just .len — a wrong borrow with the right len but a wrong ptr/cap would
// otherwise pass. borrow_i32 mutates through the slice and reads the backing
// array (alias, not copy).
package arraytoslice_test;
let g: [3]i32 = [7, 8, 9];
fn mk() []i32 = { return g; };
fn sum_i32(s: []i32) i32 = {
assert(s.cap == 4);
let t: i32 = 0; let i: i32 = 0;
for (i < s.len) { t += s[i]; i += 1; };
return t;
};
fn sum_u8(s: []u8) i32 = {
assert(s.cap == 3);
let t: i32 = 0; let i: i32 = 0;
for (i < s.len) { t += s[i]: i32; i += 1; };
return t;
};
@test fn let_i32() void = {
let a: [4]i32 = [11, 22, 33, 44];
let s: []i32 = a;
assert(s[2] == 33);
assert(s.len == 4);
assert(s.cap == 4);
};
@test fn let_u8() void = {
let a: [4]u8 = [11u8, 22u8, 33u8, 66u8];
let s: []u8 = a;
assert(s[3]: i32 == 66);
assert(s.len == 4);
assert(s.cap == 4);
};
@test fn assign_i32() void = {
let a: [4]i32 = [1, 2, 3, 4];
let s: []i32 = a[0:1];
s = a;
assert(s[3] == 4);
assert(s.len == 4);
assert(s.cap == 4);
};
@test fn callarg_i32() void = {
let a: [4]i32 = [10, 20, 30, 5];
assert(sum_i32(a) == 65);
};
@test fn callarg_u8() void = {
let a: [3]u8 = [10u8, 20u8, 40u8];
assert(sum_u8(a) == 70);
};
@test fn return_i32() void = {
let s: []i32 = mk();
assert(s.len == 3);
assert(s.cap == 3);
assert(s[1] + s[2] == 17);
};
@test fn borrow_i32() void = {
let a: [4]i32 = [1, 2, 3, 4];
let s: []i32 = a;
s[2] = 55;
assert(a[2] == 55);
};

View File

@@ -0,0 +1,46 @@
// arrlit_elem_narrow_test — #251: narrow int/rune array-literal elements to a
// declared [N]T element type at the let / def / struct-field init sites.
// Migrated from test/wcc/951_arrlit_elem_narrow_run.c (accept rows; cs==ww
// byte-id rides T2). The out-of-range and str-elem reject rows stay as runww
// //ww:error carriers under test/wcc/data/.
//
// Pre-#251 cstage REJECTED in-range bare-int/rune lits at local let/def/struct
// ("[4]i32 not assignable to [4]u8"); wwstage SILENTLY over-accepted out-of-
// range/str at def + struct-field (a rule-7 miscompile). Element WIDTH is
// declared-type-driven at cgen, so the byte-id gate confirms the emitted bytes
// are u8-wide regardless of the literal node type.
package arrlit_elem_narrow_test;
type enc = struct { m: [4]u8 };
def D_int: [4]u8 = [65, 66, 67, 68];
def D_rune: [4]u8 = ['A', 'B', 'C', 'D'];
let E: enc = enc { m = [65, 66, 67, 68] };
@test fn let_int() void = {
let a: [4]u8 = [65, 66, 67, 68];
assert(a[0]: i32 == 65);
};
@test fn let_rune() void = {
let a: [4]u8 = ['A', 'B', 'C', 'D'];
assert(a[0]: i32 == 65);
};
@test fn def_int() void = {
assert(D_int[0]: i32 == 65);
};
@test fn def_rune() void = {
assert(D_rune[0]: i32 == 65);
};
@test fn struct_int() void = {
assert(E.m[0]: i32 == 65);
};
@test fn struct_rune() void = {
let e: enc = enc { m = ['A', 'B', 'C', 'D'] };
assert(e.m[1]: i32 == 66);
};

View File

@@ -0,0 +1,85 @@
// arrlit_infer_elem_test — #103/#108/#104: an INFERRED let defaults its
// untyped-int element to `int` (8B machine word), not i32. Migrated from
// test/wcc/813_arrlit_infer_elem_run.c (value rows; cs==ww byte-id rides T2).
//
// Pre-fix cstage truncated the untyped-int default to i32 (4B): a value > 2^31
// (5000000000 → 705032704) and arrays strided at 4; wwstage SEGV'd on the
// inferred array (under-sized frame). The wide rows use 5000000000 ( > 2^31) as
// the truncation teeth — a small value would pass both stages by luck. The
// ctrl_* rows use ANNOTATED element types ([4]i32 / [4]int): the fix must leave
// those untouched, so they pin the inferred-default branch is isolated.
package arrlit_infer_elem_test;
type myb = bool;
type arr = [4]int;
type arr2 = arr;
type sl = []int;
type sl2 = sl;
@test fn arr_wide() void = {
let a = [5000000000, 2, 3, 4];
assert(a[0] == 5000000000);
assert(a[3] == 4);
};
@test fn scalar_wide() void = {
let x = 5000000000;
assert(x == 5000000000);
};
@test fn arr_small() void = {
let a = [10, 20, 30, 40];
assert(a[2] == 30);
};
@test fn m2_while() void = {
let b: myb = true;
let i = 0;
for (b) {
i = i + 1;
if (i >= 3) { b = false; };
};
assert(i == 3);
};
@test fn m8_range1() void = {
let a: arr = [1, 2, 3, 4];
let sum = 0;
for (let x .. a) { sum = sum + x; };
assert(sum == 10);
};
@test fn m8_range2() void = {
let a: arr2 = [1, 2, 3, 4];
let sum = 0;
for (let x .. a) { sum = sum + x; };
assert(sum == 10);
};
@test fn m8_slice1() void = {
let a = [10, 20, 30, 40];
let s: sl = a[1:3];
assert(s.len == 2);
assert(s[0] == 20);
assert(s[1] == 30);
};
@test fn m8_slice2() void = {
let a = [10, 20, 30, 40];
let s: sl2 = a[1:3];
assert(s.len == 2);
assert(s[0] == 20);
let t = s[0:1];
assert(t[0] == 20);
};
@test fn ctrl_i32() void = {
let a: [4]i32 = [1, 2, 3, 4];
assert(a[3] == 4);
};
@test fn ctrl_int() void = {
let a: [4]int = [1, 2, 3, 4];
assert(a[3] == 4);
};

View File

@@ -0,0 +1,60 @@
// arrlit_slice_test — #25/#31: a one-step array-LITERAL initialiser for a SLICE
// local (`let xs: []T = [..]`). Migrated from test/wcc/953_arrlit_slice_run.c
// (value rows; cs==ww byte-id rides T2). Reject rows (out-of-range element +
// the three non-let-borrow contexts) stay as runww //ww:error carriers under
// test/wcc/data/.
//
// #31 (silent cs!=ww): the borrow wrapped the un-addressable arrlit directly,
// so .ptr pointed at garbage (xs[1] returned 1, not 20; []u8/[]str SEGV). #25
// (over-strict): bare-int-width / str elements rejected. The fix re-stamps the
// arrlit [count]T and materialises a fresh per-borrow stack slot. multi_live is
// the SOUNDNESS pin: a shared backing slot would alias the two borrows (4+4=8).
package arrlit_slice_test;
@test fn i32_sum() void = {
let xs: []i32 = [10, 20, 30];
assert(xs[0] + xs[1] + xs[2] == 60);
};
@test fn i32_idx() void = {
let xs: []i32 = [10, 20, 30];
assert(xs[1] == 20);
};
@test fn u8_coerce() void = {
let zs: []u8 = [1, 2, 3];
assert(zs[0]: i32 + zs[1]: i32 + zs[2]: i32 == 6);
};
@test fn u8_typed() void = {
let xs: []u8 = [10u8, 20u8, 30u8];
assert(xs[2]: i32 == 30);
};
@test fn i64_stride() void = {
let xs: []i64 = [7i64, 42i64];
assert(xs[1] == 42);
};
@test fn str_read() void = {
let ys: []str = ["ab", "c"];
assert(ys[0].len: i32 * 10 + ys[1].len: i32 == 21);
};
@test fn len_read() void = {
let xs: []i32 = [10, 20, 30];
assert(xs.len == 3);
};
@test fn multi_live() void = {
let xs: []i32 = [1, 2, 3];
let ys: []i32 = [4, 5];
assert(xs[0] + ys[0] == 5);
};
@test fn borrow_mut() void = {
let xs: []i32 = [1, 2, 3];
xs[1] = 99;
assert(xs[1] == 99);
};

View File

@@ -0,0 +1,52 @@
// continue_test — #138: `continue` in a loop with a post-step must run the
// post-step BEFORE re-testing the cond/bound. Migrated from
// test/wcc/911_continue_run.c (value rows; cs==ww byte-id rides T2).
//
// Pre-fix the continue-target was the loop top, SKIPPING the post-step, so the
// value that triggered continue never advanced → infinite loop. Both stages
// emitted identical buggy asm, so the byte-id gate was blind; the runtime
// readback (count/sum mismatch, or a hang) is the net. The 1-clause row pins
// the no-post-step shape stays unchanged (cont-target = loop top).
package continue_test;
@test fn for3_skip_one() void = {
let count: i32 = 0;
for (let i: i32 = 0; i < 5; i += 1) {
if (i == 2) { continue; };
count += 1;
};
assert(count == 4);
};
@test fn for3_skip_two() void = {
let c: i32 = 0;
for (let i: i32 = 0; i < 5; i += 1) {
if (i == 1) { continue; };
if (i == 3) { continue; };
c += 10;
};
assert(c == 30);
};
@test fn range_skip() void = {
let xs: [5]i32 = [10, 20, 30, 40, 50];
let sum: i32 = 0;
for (let v .. xs) {
if (v == 30) { continue; };
sum += v;
};
assert(sum == 120);
};
@test fn for1_continue_byteid() void = {
let count: i32 = 0;
let i: i32 = 0;
for (i < 5) {
let cur: i32 = i;
i += 1;
if (cur == 2) { continue; };
count += 1;
};
assert(count == 4);
};

View File

@@ -0,0 +1,38 @@
// fieldfn_leaf_collide_test — #211: a value-receiver fn-pointer FIELD call whose
// leaf name `pull` collides with a same-module GLOBAL fn `pull` of a different
// register shape (field returns tagged (i64|sentinel), global returns scalar
// i64). Migrated from test/wcc/782_fieldfn_leaf_collide_run.c (cs==ww byte-id
// rides T2).
//
// Pre-fix wwstage cgen re-derived the call-result shape by NAME and mis-bound
// the scalar global, widening a 1-word AX into the tagged slot — silent cs!=ww.
// The fix reads the checker-stamped src.type_ off the N_CALL node. The global
// stays live (g = pull(3) = 14) so the collision is real, not dead-code-elided;
// `r is i64` / `r as i64` prove the result carries the tagged field type.
package fieldfn_leaf_collide_test;
type sentinel = void;
fn pull(x: i64) i64 = {
return x + 11i64;
};
type src = struct {
pull: fn(s: *src, k: i64) (i64 | sentinel),
};
fn srcpull(s: *src, k: i64) (i64 | sentinel) = {
return k + 100i64;
};
@test fn field_vs_global_leaf() void = {
let s: src;
s.pull = srcpull;
let g: i64 = pull(3i64);
let r = s.pull(&s, 5i64);
let out: i64 = -1i64;
if (r is i64) { out = r as i64; };
assert(out == 105i64);
assert(g == 14i64);
};

View File

@@ -0,0 +1,87 @@
// forrange_fieldbase_test — #70: by-value for-range over a NON-IDENT slice/str
// base (field chain, indexed element, call result). Migrated from
// test/wcc/937_forrange_fieldbase_run.c (value rows; cs==ww byte-id rides T2).
// The two LOUD-reject rows (deref base #11, non-ident array base #70) stay as
// runww //ww:error carriers under test/wcc/data/.
//
// Pre-#70 the range header stored the data POINTER into the bound temp and the
// per-iteration code reused it as the base, so i was compared against the
// pointer and the loop walked off the end (regex.finish SEGV); empty slices
// coincidentally exited on ptr==0, so the byte-id gate held on both-wrong. Now
// bound = len, base ptr spilled to its own slot. field_base_24B is the live
// SEGV repro (24B str-header elements through a ptr field); eval_once pins the
// range operand is captured once (a per-iteration field re-read would mis-sum).
package forrange_fieldbase_test;
type holder = struct { xs: []i64, name: str };
type holder2 = struct { xs: []i64, n: i64 };
type bag = struct { names: []str, n: i64 };
fn mk() []i64 = {
let xs: []i64;
append(xs, 7);
append(xs, 8);
return xs;
};
@test fn field_base() void = {
let h: holder = holder { xs = mk(), name = "h" };
let s1: i64 = 0;
for (let v .. h.xs) { s1 += v; };
assert(s1 == 15);
let p: *holder = &h;
let s2: i64 = 0;
for (let v .. p.xs) { s2 += v; };
assert(s2 == 15);
};
@test fn field_base_24B() void = {
let names: []str;
append(names, "ab");
append(names, "cde");
let b: bag = bag { names = names, n = 2 };
let p: *bag = &b;
let total: i64 = 0;
for (let nm .. p.names) {
total += (len(nm): i64);
};
assert(total == 5);
};
@test fn indexed_base() void = {
let m: [][]i64;
append(m, mk());
let s: i64 = 0;
for (let v .. m[0]) { s += v; };
assert(s == 15);
};
@test fn call_base() void = {
let s: i64 = 0;
for (let v .. mk()) { s += v; };
assert(s == 15);
};
@test fn empty_field() void = {
let h: holder = holder { name = "e", ... };
let hit: bool = false;
for (let v .. h.xs) { hit = true; };
assert(!hit);
};
@test fn eval_once() void = {
let a: []i64;
append(a, 7);
append(a, 8);
let b: []i64;
append(b, 100);
let h: holder2 = holder2 { xs = a, n = 0 };
let s: i64 = 0;
for (let v .. h.xs) {
s += v;
h.xs = b;
};
assert(s == 15);
assert(len(h.xs) == 1);
};

View File

@@ -0,0 +1,27 @@
// nullable_assert_test — #17 (F4): `e as T` on a nullable `(*T|void)` operand
// must discriminate POINTER-vs-NULL, not compare the pointer against a variant
// tag index. Migrated from test/wcc/949_nullable_assert_run.c (success-path
// value rows; cs==ww byte-id rides T2). The must-abort-on-mismatch polarity
// rows stay as a carrier (an abort cannot be a @test row).
//
// For a nullable operand the slot word IS the pointer (no tag word, zero-cost
// fold — NOT a 2-variant tagged union, which would exercise the wrong
// lowering). Pre-fix wwstage cgtypeassert CMPQ'd against the pointer and
// unwrapped past the 8B slot. asrt_ptr_ok exercises the success arm (valid ptr
// unwrap + deref); asrt_void_target_ok exercises the void-target arm.
package nullable_assert_test;
@test fn asrt_ptr_ok() void = {
let x: int = 42;
let p: (*int|void) = &x;
assert(p is *int);
let q: *int = p as *int;
assert((*q): i32 == 42);
};
@test fn asrt_void_target_ok() void = {
let p: (*int|void) = void;
assert(p is void);
let z = p as void;
};

View File

@@ -0,0 +1,37 @@
// nullable_try_test — #15 (F4): the `?` / `!` try operators on a nullable
// `(*T|void)` operand must discriminate POINTER-vs-NULL, not tag-vs-success-
// index. Migrated from test/wcc/949_nullable_try_run.c (non-abort value rows;
// cs==ww byte-id rides T2). The must-abort-on-null polarity (`!` on null) stays
// as a carrier (an abort cannot be a @test row).
//
// For a nullable operand AX holds the POINTER and successtag()=0, so pre-fix
// wwstage cgtryprop/cgtryunw CMPQ'd $0,AX and treated NULL as success / a valid
// pointer as the error (inverted). tryprop_valid pins the valid-ptr unwrap +
// mutate; tryprop_null pins NULL propagates to the caller (pre-fix: null deref
// SEGV); tryunw_valid pins the `!` valid-ptr unwrap.
package nullable_try_test;
fn gp(p: (*int|void)) (*int|void) = { let q = p?; *q = 7; return q; };
fn hu(p: (*int|void)) int = { let q = p!; return *q; };
@test fn tryprop_valid() void = {
let x: int = 1;
let r = gp(&x);
assert(x == 7);
};
@test fn tryprop_null() void = {
let r = gp(void);
let isvoid: bool = false;
match (r) {
case let q: *int => { };
case void => { isvoid = true; };
};
assert(isvoid);
};
@test fn tryunw_valid() void = {
let x: int = 42;
assert(hu(&x): i32 == 42);
};

View File

@@ -0,0 +1,52 @@
// slice_global_arg_runonly_test — #148 (D2): a module-global slice passed BY
// VALUE as a slice arg must arrive with a real header, not garbage. Migrated
// from test/wcc/953_globalslice_arg_run.c.
//
// _runonly: CSTAGE-ONLY. wwstage's checker rejects a module-level `const []T`
// global ("let: not assignable", filed task #28), so this file does not compile
// under w6c_ww and is excluded from the T2 byte-id corpus. test-lang runs it
// through the cstage `ww test` only.
//
// Pre-fix the slice-ident call-arg fast path emitted BP-relative pushes for a
// global (localfind→0), reading saved-BP/RIP garbage instead of the global's
// header at name(SB). The fix mirrors the N_SLICE arm's isglobal dispatch. The
// callee reads .len AND a byte, so a wrong header is observable; u32_global
// pins the path is element-width-agnostic; direct_read + local_arg lock the
// in-place read and the off!=0 local arm against regression.
package slice_global_arg_runonly_test;
const dotdot: []u8 = ['.', '.'];
const dot: []u8 = ['.'];
const g: []u32 = [7u32, 8u32, 9u32];
const d: []u32 = [11u32, 22u32];
fn seen_u8(bs: []u8) i32 = {
return bs.len: i32 * 1000 + bs[0]: i32;
};
fn seen_u32(xs: []u32) i32 = {
return xs.len: i32 * 100 + xs[0]: i32 + xs[2]: i32;
};
@test fn u8_dotdot() void = {
assert(seen_u8(dotdot) == 2046);
};
@test fn u8_dot() void = {
assert(seen_u8(dot) == 1046);
};
@test fn u32_global() void = {
assert(seen_u32(g) == 316);
};
@test fn direct_read() void = {
assert(d.len: i32 * 100 + d[0]: i32 + d[1]: i32 == 233);
};
@test fn u8_local_arg() void = {
let a: [2]u8 = ['.', '.'];
let s: []u8 = a[0:2];
assert(seen_u8(s) == 2046);
};

View File

@@ -0,0 +1,26 @@
// type_value_shadow_test — #225: a value binding that shadows a same-named TYPE
// must NOT hide that type in type-annotation or cast position. ww keeps type and
// value namespaces separate. Migrated from test/wcc/788_type_value_shadow_run.c
// (value rows; cs==ww byte-id rides T2). The value-in-type-position reject row
// (no same-named type) stays as a carrier pin.
//
// Pre-fix cstage resolved the type name via the kind-blind lookup, so the inner
// value shadowed the global type → "unknown type". The fix is a kind-filtered
// type lookup that scans past non-type bindings. Both annotation (param type)
// and cast-target route through it, so the two rows cover both positions.
package type_value_shadow_test;
type off = i64;
type t = i32;
fn f(off: off) i64 = { return off: off; };
@test fn param_and_cast() void = {
assert(f(5i64) == 5i64);
};
@test fn cast_local() void = {
let t: i32 = 7;
assert((t: t) == 7);
};