test: migrate str-elem-cap/size-in-type behavior to test/lang @test

Two more value-observable families ported additively (the .c sources
keep running in $(TESTS); de-dup deferred to fold 6).

  932_str_elem_cap -> str_elem_cap_test: a str-element N_INDEX value
    read must load the full 24B {ptr,len,cap}, not {ptr,len} (F2); each
    shape poisons cap != len so a 2-word read fails the .cap assert.
    Template for the 933-939 cap family.
  957_size_type -> size_type_test: `size` binds in type position and
    coexists with the size(T) operator (#85); a green row is the bind
    proof.
This commit is contained in:
2026-06-22 03:01:09 +09:00
parent 7db30bf609
commit 08975c11c9
2 changed files with 112 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
// size_type_test — the `size` type name must be usable in TYPE position (#85
// fold-2) AND coexist with the `size(T)` size-of OPERATOR, migrated from
// test/wcc/957_size_type_run.c. The two are different paths: TYPE-position
// `size` resolves via the type-name resolver (lookup_builtin / N_TNAME chain),
// while `size(T)` is a c.top SK_FN seed folded to an N_INTLIT — so a program
// may use BOTH in the same scope with no clash. Without the fold-2 resolver
// arm a `let _: size` declaration fails to compile ("unknown type 'size'"), so
// a GREEN row IS the bind proof: if `size` didn't bind as a type, the file
// would not compile. `size` mirrors uintptr: platform-width (8B amd64)
// unsigned int. cstage-only by design (mirrors 951/952/969); the wwstage
// resolver arm is twinned for rule-10 symmetry and stays dead on the size-free
// selfhost corpus.
//
// The rows vary the size-typed value (0, 42, sum, struct field) and the
// consuming context (comparison, arithmetic, i32-cast); the SHAPE — `size` in
// a let-binding vs a struct-field type — is what the bug gates on, so the
// shapes are spread across @test fns and the operator-coexistence row sits
// alongside the type-position rows.
package size_type_test;
type box = struct { n: size };
@test fn size_type_and_operator_coexist() void = {
// type-position `size` + the size(T) operator in one scope; both resolve
// and agree (8/4 on amd64).
let x: size = 0;
assert(x == 0);
let y: size = 42;
assert(y == 42);
assert(size(int) == 8);
assert(size(i32) == 4);
};
@test fn size_arith_cast() void = {
// a size-typed sum truncates to i32 and propagates (40 + 2 == 42).
let x: size = 40;
let y: size = 2;
let r: i32 = (x + y): i32;
assert(r == 42);
};
@test fn size_struct_field() void = {
// `size` as a struct field type: 8-byte slot, written and read back.
let b: box = box{ n = 7 };
assert(b.n: i32 == 7);
};
@test fn size_operator_cast() void = {
// the size(T) operator value-propagated through an i32 cast (operator
// path, must keep working alongside the type-name arm).
let r: i32 = size(int): i32;
assert(r == 8);
};

View File

@@ -0,0 +1,58 @@
// str_elem_cap_test — a str-element VALUE read via N_INDEX must load the full
// 24B {ptr,len,cap} header, not just {ptr,len}, migrated from test/wcc/
// 932_str_elem_cap_run.c (F2 fold). str is 24B since Phase 2 (#1); pre-F2 the
// N_INDEX str-element arms dropped the cap word, so reading `.cap` off an
// indexed str element returned garbage (the missing third word) — cs==ww held,
// so the 990-997 byte-id gates were GREEN while the runtime was wrong.
//
// Each shape POISONS the element so cap != len (a `.cap =` pseudo-field write,
// no malloc / no import), then observes cap through `let e: str = <index>` (a
// 3-word copy into the slot) and `e.cap` (an N_IDENT pseudo-field read off the
// slot). A 2-word read leaves cap = len (N_IDENT base) or a stale slice-cap
// (fallback base), so the read-back .cap mismatches the poisoned value. .len is
// the control. The shapes (index base form), not the data, are what vary, so
// per-shape asserts — this file is the template for the 933-939 cap family.
package str_elem_cap_test;
type box = struct { items: []str };
@test fn sitea_ident_base() void = {
// N_IDENT base: xs[0] on a local [N]str, constant index. Poison cap=8
// (len=2); a broken 2-word read leaves cap = len = 2.
let p: str = "hi";
p.cap = 8i32;
let xs: [2]str;
xs[0] = p;
let e: str = xs[0];
assert(e.cap: i32 == 8);
assert(e.len: i32 == 2);
};
@test fn sitea_var_index() void = {
// N_IDENT base indexed by a VARIABLE (not a constant) — the index
// source must not change which words the element load reads.
let p: str = "hey";
p.cap = 12i32;
let xs: [2]str;
let i: i32 = 1;
xs[i] = p;
let e: str = xs[i];
assert(e.cap: i32 == 12);
assert(e.len: i32 == 3);
};
@test fn siteb_fallback_base() void = {
// Fallback base: the element sits behind an N_DOT slice field, so the
// N_INDEX base isn't a plain ident. Poison cap=9 (len=5); a broken read
// leaves cap = the slice header's own cap, not the element's.
let q: str = "world";
q.cap = 9i32;
let arr: [2]str;
arr[0] = q;
let b: box;
b.items = arr[0:2];
let e: str = b.items[0];
assert(e.cap: i32 == 9);
assert(e.len: i32 == 5);
};