test: migrate 802_lenidx to @test + runww rejects, retire C twin (fold-3)
#19 len/index family: value rows -> test/lang/lenidx_test.ww (primitive-only asserts), 3 reject (callres/sliceexpr/strlit) -> runww //ww:error dual-stage carriers. byteid floor 52->53.
This commit is contained in:
139
test/lang/lenidx_test.ww
Normal file
139
test/lang/lenidx_test.ww
Normal file
@@ -0,0 +1,139 @@
|
||||
// lenidx_test — the `len()` builtin over a non-IDENT operand shape (#19, then
|
||||
// the C5 sweep #10 F2 + #41 FA2/FB1), migrated from test/wcc/802_lenidx_run.c.
|
||||
// `len(xs[i])` / `len(*p)` / `len(s.field)` etc. once fell to a bare cgexpr
|
||||
// fallback that left AX = the slice/str DATA POINTER, so len() returned the
|
||||
// PTR as the length — silent ptr-garbage, byte-identical in BOTH stages (the
|
||||
// bootstrap corpus never indexes-then-len()s an element, so byte-id was
|
||||
// blind). The fix routes every place-resolvable operand through one uniform
|
||||
// header-place arm (.len at place+8) in both stages; non-place operands
|
||||
// (string literal, slicing expr, call result) now die LOUD (rule 7) — those
|
||||
// three are the runww reject carriers. The empty/zero rows are the strongest
|
||||
// pins: a leaked ptr is nonzero, so `len(empty) == 0` catches the regression
|
||||
// where a bare exit code couldn't. PRIMITIVE-only asserts (no fmt/strconv) so
|
||||
// a co-miscompile in the assert path cannot mask the bug. The .len/.ptr
|
||||
// pseudo-field control pins that the fix left the already-correct field paths
|
||||
// untouched. T2 keeps cs==ww (the #19 lineage: len of an INDEXED element is
|
||||
// exactly where cs!=ww would surface).
|
||||
|
||||
package lenidx_test;
|
||||
|
||||
// global [N]str table — the exact #19 repro operand (static-init str array,
|
||||
// #18 family); referenced from several rows to keep the global codegen path.
|
||||
let t: [3]str = ["a", "bcd", ""];
|
||||
|
||||
type S = struct { pad: i64, name: str };
|
||||
|
||||
@test fn lenidx_arrtab_mid() void = {
|
||||
// len(t[1]) == 3; pre-fix this returned a ptr low-byte, not 3.
|
||||
assert(len(t[1]) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_arrtab_empty() void = {
|
||||
// empty element: a leaked ptr is nonzero, len must be 0.
|
||||
assert(len(t[2]) == 0);
|
||||
};
|
||||
|
||||
@test fn lenidx_arrtab_first() void = {
|
||||
// guards an off-by-one in the index scale.
|
||||
assert(len(t[0]) == 1);
|
||||
};
|
||||
|
||||
fn slen(xs: []str, i: i64) int = { return len(xs[i]): int; };
|
||||
|
||||
@test fn lenidx_slice_param_sum() void = {
|
||||
// len(xs[i]) over a []str slice ARG (callee-side shape), 1 + 3 + 0 == 4.
|
||||
let arr: [3]str = ["a", "bcd", ""];
|
||||
let xs: []str = arr;
|
||||
assert(slen(xs, 0) + slen(xs, 1) + slen(xs, 2) == 4);
|
||||
};
|
||||
|
||||
@test fn lenidx_neg_control_field_agree() void = {
|
||||
// the `.len` pseudo-field on the same indexed element must agree with
|
||||
// len(), AND `.ptr` must still deref to the first byte — the fix must
|
||||
// leave the already-correct .len/.ptr field paths untouched.
|
||||
assert(len(t[1]) == t[1].len);
|
||||
let p: *u8 = t[1].ptr;
|
||||
assert(*p == 'b');
|
||||
assert(len(t[1]) == 3);
|
||||
};
|
||||
|
||||
fn derefn(p: *[]i64) int = { return len(*p): int; };
|
||||
|
||||
@test fn lenidx_deref_param() void = {
|
||||
// len(*p) through a *[]T PARAM (FB1) loaded the header's word 0 (the
|
||||
// data pointer) as the length.
|
||||
let xs: []i64 = [10, 20, 30];
|
||||
assert(derefn(&xs) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_deref_local() void = {
|
||||
let xs: []i64 = [10, 20, 30];
|
||||
let p: *[]i64 = &xs;
|
||||
assert(len(*p) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_deref_empty() void = {
|
||||
// EMPTY-slice deref: the off-by-header bug returns .ptr (nonzero),
|
||||
// len() must say 0 — only a non-exit primitive assert pins this.
|
||||
let xs: []i64 = [1];
|
||||
let ys: []i64 = xs[0:0];
|
||||
let p: *[]i64 = &ys;
|
||||
assert(len(*p) == 0);
|
||||
};
|
||||
|
||||
@test fn lenidx_deref_chain() void = {
|
||||
// chained deref len(**pp) — the resolver spine must recurse both hops.
|
||||
let xs: []i64 = [10, 20, 30];
|
||||
let p: *[]i64 = &xs;
|
||||
let pp: **[]i64 = &p;
|
||||
assert(len(**pp) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_idx_field() void = {
|
||||
// len(xs[i].field) (F2) — N_DOT over an N_INDEX base matched no arm.
|
||||
let xs: [2]S = [S{ pad = 1, name = "a" }, S{ pad = 2, name = "bcde" }];
|
||||
assert(len(xs[1].name) == 4);
|
||||
};
|
||||
|
||||
fn derefidxn(p: *[]S, i: i64) int = { return len((*p)[i].name): int; };
|
||||
|
||||
@test fn lenidx_deref_idx_field() void = {
|
||||
// full deref spine: len((*p)[i].field).
|
||||
let arr: [2]S = [S{ pad = 1, name = "a" }, S{ pad = 2, name = "bcde" }];
|
||||
let xs: []S = arr;
|
||||
assert(derefidxn(&xs, 1) == 4);
|
||||
};
|
||||
|
||||
fn derefidxcn(p: *[]S, i: i64) int = { return len((*p)[i + 1].name): int; };
|
||||
|
||||
@test fn lenidx_deref_idx_field_computed() void = {
|
||||
// computed index through the spine — i+1 must resolve like a constant.
|
||||
let arr: [2]S = [S{ pad = 1, name = "a" }, S{ pad = 2, name = "bcde" }];
|
||||
let xs: []S = arr;
|
||||
assert(derefidxcn(&xs, 0) == 4);
|
||||
};
|
||||
|
||||
@test fn lenidx_dot_field() void = {
|
||||
// len(s.field) — the #235 arm's inner non-tuple fallback was ptr-garbage.
|
||||
let s: S = S{ pad = 7, name = "abc" };
|
||||
assert(len(s.name) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_ptr_field() void = {
|
||||
// len(p.field) through a *struct.
|
||||
let s: S = S{ pad = 7, name = "abc" };
|
||||
let p: *S = &s;
|
||||
assert(len(p.name) == 3);
|
||||
};
|
||||
|
||||
@test fn lenidx_neutral_array_const() void = {
|
||||
// neutrality control — the [N]T const fast-path still holds.
|
||||
let a: [5]u8 = [1, 2, 3, 4, 5];
|
||||
assert(len(a) == 5);
|
||||
};
|
||||
|
||||
@test fn lenidx_neutral_ident_str() void = {
|
||||
// neutrality control — the ident-str fast-path still holds.
|
||||
let s: str = "abcd";
|
||||
assert(len(s) == 4);
|
||||
};
|
||||
Reference in New Issue
Block a user