test: migrate str/slice global family-1 batch to test/lang @test, retire C twins (fold-2)

Continue fold-2 (after 374e97b): migrate the remaining Family-1 str/slice
global + literal + index + call-arg group from bespoke build+run C twins to
test/lang @test, retiring each twin in the same commit. Runtime coverage MOVES
from $(TESTS) to test-lang (T1 runs+asserts via `ww test`) + test-lang-byteid
(T2 keeps cs==ww .s byte-id); coverage is preserved, the $(TESTS) headline
drops 9 (434->425). All asserts are primitive int/u8/bool comparisons (no
fmt/strconv in the assert path); the slice-store/index rows reset the global
each fn and sum ADJACENT elements so a dropped/mis-strided/over-wide word FAILS.

  989_globslicefield_run.c    -> glob_slice_field_test.ww     (slice field of a global struct: g.f=<slice> stores full 24B header; len/cap/non-zero-offset + str/scalar controls)
  989_globstrslice_run.c      -> glob_str_slice_arg_test.ww   (global str sliced with default hi passed as call arg loads its len word; explicit-hi control)
  989_trystr_run.c            -> try_str_unwrap_test.ww       (`!` unwrap of str-success tagged union shuffles the str header for ident-source/error-first/success-first)
  797_len_strglobal_run.c     -> len_str_global_test.ww       (len(str-global) loads .len via name(SB); local-str control)
  801_litstr_pseudo_run.c     -> lit_str_pseudo_test.ww       (string-literal .len/.ptr pseudo-field; empty/multibyte + arg-passthrough)
  803_globalidx_run.c         -> global_index_test.ww         (global str/slice index read/addr-of/store/compound, esz 1/4; local regression pins)
  903_tuple_elem_slice_len.c  -> tuple_elem_slice_len_test.ww (len(t.N) of a slice/str tuple element loads .len at +8; 2/3-slice, str-slice both orders)
  927_composite_call_arg_run.c-> composite_call_arg_test.ww   (slice-returning CALL passed inline as a composite arg; canonical/letslice/two-call/middle/nested/scalar/tagged)
  952_slicecopy_assign_run.c  -> slice_copy_assign_test.ww    (bulk slice-copy-assign `arr[lo:hi]=bs`, esz 1/4, field/via-ptr/local bases; reslice-read companion)

rd_reslice asserts the TRUE value 360 (the .c twin's want=104 was 360 & 0xFF,
an exit-code truncation). 723_composite_call_arg.c's comment repointed to the
new test/lang location. 802_lenidx_run.c is DEFERRED (it carries //ww:error
reject rows — needs a value-rows-only split + a slim reject carrier, a fold-3
pass). Bump LANGBYTEID_EXPECTED_MIN 22->31 to ratchet the new corpus floor.
This commit is contained in:
2026-06-22 09:30:28 +09:00
parent 374e97b9e8
commit 438efab8c6
20 changed files with 623 additions and 2180 deletions

View File

@@ -0,0 +1,122 @@
// composite_call_arg_test — a 3-reg composite (`[]u8` slice) CALL result passed
// inline as a composite arg to another call, migrated from
// test/wcc/927_composite_call_arg_run.c (#24, Class A wwstage cgen miscompile).
// Pre-fix wwstage emitted a single `PUSHQ AX` for a slice-returning CALL arg
// (losing .len/.cap) and under-popped the receiver's arg-regs by two words: the
// arg-shift collision corrupts every subsequent arg (the receiver reads the
// caller's spilled p.ptr as its own s.len). The fix gave `nodeisslice` an N_CALL
// arm (mirroring nodeisstr): both the push side (3-PUSH CX,BX,AX) and the pop
// side (extra=2) fire for slice-returning CALLs as args. Each @test KEEPS the
// `check(view(s))` call-arg-composite shape under test and asserts the receiver
// saw the right .len (a check fn returns 0 only when its internal .len checks
// pass, so the asserted 0 IS the value contract).
package composite_call_arg_test;
fn view(s: str) []u8 = {
let r: []u8;
r.ptr = s.ptr;
r.len = s.len;
r.cap = s.len;
return r;
};
fn passthrough(a: []u8) []u8 = { return a; };
fn check_a(a: []u8) i32 = {
if (a.len == 13) { return 0; };
return 11;
};
fn check2_b(a: []u8, b: []u8) i32 = {
if (a.len != 13) { return 11; };
if (b.len != 5) { return 12; };
return 0;
};
fn check2_c(a: []u8, b: []u8) i32 = {
if (a.len != 3) { return 11; };
if (b.len != 5) { return 12; };
return 0;
};
fn check3_d(k0: i32, a: []u8, k1: i32) i32 = {
if (k0 != 7) { return 11; };
if (a.len != 4) { return 12; };
if (k1 != 9) { return 13; };
return 0;
};
fn check_e(a: []u8) i32 = {
if (a.len == 6) { return 0; };
return 11;
};
fn use2_f(a: []u8, k: i32) i32 = {
if (a.len != 13) { return 11; };
if (k != 99) { return 12; };
return 0;
};
type oserror = !i32;
fn yield_ptr() (*u8 | oserror) = {
let p: *u8 = nil;
return p;
};
fn dispatch(v: (*u8 | oserror)) i32 = {
match (v) {
case let p: *u8 => return 7;
case let e: oserror => return e: i32;
};
return -99;
};
@test fn canonical_slice_call() void = {
// (a) canonical f(g()) with g returning []u8.
let s: str = "hello, world!";
assert(check_a(view(s)) == 0);
};
@test fn slice_call_then_letslice() void = {
// (b) f(g(), p) — strings.hasprefix shape: slice-CALL then a let-slice.
let s: str = "hello, world!";
let p: []u8;
p.ptr = s.ptr;
p.len = 5;
p.cap = 5;
assert(check2_b(view(s), p) == 0);
};
@test fn two_composite_calls_args() void = {
// (c) f(g(in1), g(in2)) — two composite CALLs; the stack must hold both
// 3-word headers before the 6-POP drain.
let s1: str = "foo";
let s2: str = "hello";
assert(check2_c(view(s1), view(s2)) == 0);
};
@test fn slice_call_middle_arg() void = {
// (d) f(k0, g(), k1) — slice-CALL in the middle of three args.
let s: str = "abcd";
assert(check3_d(7, view(s), 9) == 0);
};
@test fn nested_composite_calls() void = {
// (e) f(g(h(s))) — composite-in-composite nesting.
let s: str = "abcdef";
assert(check_e(passthrough(view(s))) == 0);
};
@test fn slice_call_then_scalar() void = {
// (f) f(g(), 99) — slice-CALL then a scalar (pop-count mix).
let s: str = "hello, world!";
assert(use2_f(view(s), 99) == 0);
};
@test fn tagged_call_regression() void = {
// (g) tagged-CALL alongside the slice path — confirms #21's natural-push
// arm composes with #24's.
assert(dispatch(yield_ptr()) == 7);
};

View File

@@ -0,0 +1,58 @@
// glob_slice_field_test — a SLICE field of a module-GLOBAL struct, assigned
// with `g.field = <slice>`, must store the full {ptr,len,cap} 24B header (not
// just {ptr}), migrated from test/wcc/989_globslicefield_run.c (F8-c1, #35).
// The wwstage single-dot global-struct field-assign gated the 3-word DX store
// on isstrtype ONLY, so a non-str slice field fell to the 1-word scalar store
// and silently DROPPED .len+.cap; the fix widens to isstrtype||isslicetype
// (align UP to cstage's TY_STR||TY_SLICE). cs==ww held byte-id-blind, so a
// behavioral @test is the net. A dropped .len/.cap reads back 0 (a fresh global
// is zeroed), so len()/.cap directly catch it; glob_off_len pins the non-zero
// field offset; the str/scalar rows are regression controls for the pre-existing
// arm and the 1-word store the slice arm must not steal.
package glob_slice_field_test;
type box1 = struct { sl: []i64 };
type box2 = struct { pad: i64, sl: []i64 };
type box3 = struct { name: str };
type box4 = struct { n: i64 };
let g1: box1;
let g2: box2;
let g3: box3;
let g4: box4;
@test fn glob_slice_len() void = {
let b: [4]i64 = [10, 11, 12, 13];
g1.sl = b[0:3];
assert(len(g1.sl): i32 == 3);
};
@test fn glob_slice_cap() void = {
let b: [4]i64 = [10, 11, 12, 13];
g1.sl = b[0:3];
assert(g1.sl.cap: i32 == 4);
};
@test fn glob_off_len() void = {
// slice field at a NON-ZERO field offset (pad@0, sl@8) pins the
// DX+foff+8 header store.
let b: [4]i64 = [10, 11, 12, 13];
g2.pad = 99;
g2.sl = b[0:2];
assert(g2.pad == 99);
assert(len(g2.sl): i32 == 2);
};
@test fn glob_str_len() void = {
// str field on a global struct — the pre-existing isstrtype arm; a
// regression pin that the widened gate keeps correct.
g3.name = "hello";
assert(len(g3.name): i32 == 5);
};
@test fn glob_scalar_n() void = {
// scalar field — the generic 1-word store the slice arm must not divert.
g4.n = 7;
assert(g4.n: i32 == 7);
};

View File

@@ -0,0 +1,34 @@
// glob_str_slice_arg_test — a module-GLOBAL str sliced with a DEFAULT high
// bound and passed as a call arg (`take(g[1:])`) must load its length word for
// the hi, migrated from test/wcc/989_globstrslice_run.c (F8-c2, #47). The
// wwstage N_SLICE call-arg pusharg had no global-str (N_TNAME "str") arm for a
// default hi, so AX kept the base pointer and got PUSHQ'd as the hi → len =
// ptr-lo, garbage; the fix emits the same `LEAQ g(SB),CX; MOVQ 8(CX),AX` len
// load as the global-slice arm (str IS []u8, len@+8), aligning UP to cstage.
// cs==ww was byte-id-blind; the .len asserts pin the runtime contract; the .ptr
// row proves the pointer word survives, the explicit-hi row is the control.
package glob_str_slice_arg_test;
let g: str = "hello";
fn slen(s: str) i32 = { return len(s): i32; };
fn sfirst(s: str) i32 = { return s[0]: i32; };
@test fn gstr_defhi() void = {
assert(slen(g[1:]) == 4);
};
@test fn gstr_defhi_full() void = {
assert(slen(g[0:]) == 5);
};
@test fn gstr_defhi_ptr() void = {
// the ptr word is still correct: g[1:][0] == 'e' == 101.
assert(sfirst(g[1:]) == 101);
};
@test fn gstr_explicit() void = {
// explicit hi (hi != nil path) must not regress.
assert(slen(g[1:3]) == 2);
};

View File

@@ -0,0 +1,143 @@
// global_index_test — the GLOBAL `str` / GLOBAL slice index family (s/g are
// module-level lets): the READ `s[i]`/`g[i]` (#10), the ADDR-OF `&s[i]`/`&g[i]`
// and the STORE `g[i] = v` (#11), migrated from test/wcc/803_globalidx_run.c.
// wwstage's cgindex dispatched the element size + base off the base's tnode KIND
// (only N_TARRAY / N_TPTR), so a global str (N_TNAME "str") and a global slice
// (N_TSLICE) matched neither arm: esz stayed 8 and the base fell to the
// wide-header fallback — an 8-byte stride + full-word MOVQ, reading 8 bytes at
// ptr+8 instead of the single byte at ptr+1. The store was an 8-byte
// OUT-OF-BOUNDS MOVQ instead of MOVB. cstage dispatched esz off the RESOLVED
// base TYPE (uniform), so it was correct. The fix aligns cgindex/cgun/cgassign
// UP to that type-driven dispatch. The slice rows reset the global each fn and
// sum ADJACENT elements so a mis-strided / over-wide write FAILS; local rows are
// regression pins for the already-clean local path.
package global_index_test;
let s: str = "ABC";
let g: []u8;
let gi: []i32;
@test fn gstr_first() void = {
assert(s[0]: i32 == 65);
};
@test fn gstr_mid() void = {
// the exact #10 repro: s[1] == 'B' == 66 (pre-fix an 8-byte word at ptr+8).
assert(s[1]: i32 == 66);
};
@test fn gstr_last() void = {
assert(s[2]: i32 == 67);
};
@test fn gstr_sum() void = {
// two byte indices summed (65+66) pins the load width — a full-word load
// would carry the high bytes.
assert(s[0]: i32 + s[1]: i32 == 131);
};
@test fn gslice_mid() void = {
let a: [3]u8 = [10u8, 20u8, 30u8];
g = a;
assert(g[1]: i32 == 20);
};
@test fn gislice_mid() void = {
// WIDTH>1, SIGNED: g[1]-g[0] == 20-(-5) == 25 pins esz=4 + sign-extend.
let a: [3]i32 = [-5, 20, 30];
gi = a;
assert(gi[1] - gi[0] == 25);
};
@test fn gstr_addr() void = {
// #11 ADDR-OF, global str: &s[1] read back == 'B' == 66.
let p: *u8 = &s[1];
assert((*p): i32 == 66);
};
@test fn gslice_addr() void = {
// #11 ADDR-OF, global []u8: &g[1] read back == 20.
let a: [3]u8 = [10u8, 20u8, 30u8];
g = a;
let p: *u8 = &g[1];
assert((*p): i32 == 20);
};
@test fn gislice_addr() void = {
// #11 ADDR-OF, global []i32 WIDTH>1: &g[1] == 20 pins esz=4 stride.
let a: [3]i32 = [-5, 20, 30];
gi = a;
let p: *i32 = &gi[1];
assert(*p == 20);
};
@test fn gslice_store() void = {
// #11 STORE, global []u8: g[1]=99; g[1]+g[0]+g[2] == 99+10+30 == 139. The
// g[0]/g[2] addends are the OOB-WRITE GUARD (pre-fix a full-word MOVQ at an
// 8-byte stride wrote 99 at ptr+8 and left g[1]==20).
let a: [3]u8 = [10u8, 20u8, 30u8];
g = a;
g[1] = 99u8;
assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139);
};
@test fn gislice_store() void = {
// #11 STORE, global []i32 SIGNED: g[1]=42; g[1]+g[0]+g[2] == 42+(-5)+30 == 67.
let a: [3]i32 = [-5, 20, 30];
gi = a;
gi[1] = 42;
assert(gi[1] + gi[0] + gi[2] == 67);
};
@test fn gslice_compound() void = {
// #11 COMPOUND STORE, global []u8: g[1] += 79 → 99; sum == 139. The third
// fixed arm (load-combine-store in place), pre-fix an 8-byte OOB RMW.
let a: [3]u8 = [10u8, 20u8, 30u8];
g = a;
g[1] += 79u8;
assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139);
};
@test fn gislice_compound() void = {
// #11 COMPOUND STORE, global []i32 SIGNED: g[1] += 47 → 42; g[1]+g[0]+g[2]
// == 42+(-25)+30 == 47.
let a: [3]i32 = [-25, -5, 30];
gi = a;
gi[1] += 47;
assert(gi[1] + gi[0] + gi[2] == 47);
};
@test fn lstr_addr() void = {
// regression pin: local str ADDR-OF (already clean) — &s[1] == 66.
let s: str = "ABC";
let p: *u8 = &s[1];
assert((*p): i32 == 66);
};
@test fn lslice_store() void = {
// regression pin: local slice STORE (already clean) — g[1]=99; sum == 139.
let a: [3]u8 = [10u8, 20u8, 30u8];
let g: []u8 = a;
g[1] = 99u8;
assert(g[1]: i32 + g[0]: i32 + g[2]: i32 == 139);
};
@test fn lstr_mid() void = {
// regression pin: local str index (already clean) — s[1] == 66.
let s: str = "ABC";
assert(s[1]: i32 == 66);
};
@test fn lslice_last() void = {
// regression pin: local slice index, last elem (already clean) — g[2] == 30.
let a: [3]u8 = [10u8, 20u8, 30u8];
let g: []u8 = a;
assert(g[2]: i32 == 30);
};
@test fn larr_mid() void = {
// regression pin: direct local array index (already clean) — a[1] == 20.
let a: [3]u8 = [10u8, 20u8, 30u8];
assert(a[1]: i32 == 20);
};

View File

@@ -0,0 +1,22 @@
// len_str_global_test — `len(str-global)` must load the .len word, migrated
// from test/wcc/797_len_strglobal_run.c (#231). BOTH stages were wrong,
// DIFFERENTLY (byte-id was blind because they also diverged, but no bootstrap
// source exercised the shape): cstage's len() arm took the BP-relative slot
// load for a top-level global (localfind returns 0 → bogus `MOVQ 8(BP),AX`);
// wwstage's arm only handled locals, so a global fell to cgexpr and left
// AX=.ptr. The fix emits the global .len load `LEAQ name(SB),CX; MOVQ 8(CX),AX`
// in both, routed through the post-#1 value mangle. The local-str case was
// correct in both (control).
package len_str_global_test;
let g: str = "hello";
@test fn str_global_len() void = {
assert(len(g): i32 == 5);
};
@test fn str_local_len() void = {
let g: str = "hello";
assert(len(g): i32 == 5);
};

View File

@@ -0,0 +1,39 @@
// lit_str_pseudo_test — the string-LITERAL `.len` / `.ptr` pseudo-field,
// migrated from test/wcc/801_litstr_pseudo_run.c (#14). cstage was wrong,
// wwstage correct (a rule-10 divergence): `"hi".len` returned the POINTER — a
// string literal is TY_UNTYPED_STR (not TY_STR), so it missed the typed str
// pseudo-field gate in cgen.c's N_DOT and fell to `cgexpr(lhs)` (AX=.ptr)
// without the BX->AX len shuffle that wwstage's cgdot catch-all already did.
// byte-id was BLIND: no bootstrap source uses literal `.len` (devs hardcoded
// the lengths). The fix aligns cstage UP — the N_DOT fallback emits `MOVQ BX,AX`
// for `.len`; `.ptr` already returned AX. The str-VARIABLE `.len` was a control.
package lit_str_pseudo_test;
fn slen(s: str) i32 = { return s.len: i32; };
@test fn lit_len() void = {
assert("hello".len: i32 == 5);
};
@test fn lit_len_empty_multibyte() void = {
// "" is 0 bytes; "héllo\n" is 7 bytes (h, é=2 UTF-8 bytes, l, l, o, \n) —
// a ptr would not read 7, so this guards a .ptr/.len confusion.
assert("".len: i32 == 0);
assert("héllo\n".len: i32 == 7);
};
@test fn lit_ptr_deref() void = {
// .ptr still points at the bytes (first byte == 'h'), proving the fix left
// .ptr untouched; then .len == 5.
let p: *u8 = "hello".ptr;
assert(*p == 'h');
assert("hello".len: i32 == 5);
};
@test fn lit_arg_passthrough() void = {
// callee-side .len on a literal passed as a str arg — the real-world shape
// (os.write(1, lit.ptr, lit.len)) that surfaced #14.
assert(slen("abcdef") == 6);
assert("abcdef".len: i32 == 6);
};

View File

@@ -0,0 +1,98 @@
// slice_copy_assign_test — bulk slice-copy-assign into a range place
// `s.arr[lo:hi] = bs` (the LHS is an N_SLICE), migrated from
// test/wcc/952_slicecopy_assign_run.c (#145). No legacy N_ASSIGN arm caught an
// N_SLICE LHS, so the statement fell through to the scalar tail and emitted
// NOTHING — a silent no-op in BOTH stages, byte-id-green (#263-class). The fix
// reuses the N_SLICE READ lowering (AX = base+lo*esz, BX = hi-lo, CX = cap),
// scales BX by the SAME read-path element width (esz; [N]u8→1, [N]i32→4), and
// emits a runtime-counted byte-granular copy loop. WRITE rows assert the copy
// lands the right bytes in [lo,hi) AND leaves bytes OUTSIDE the range untouched
// (each sums members both inside and outside, so a mis-strided / over-wide copy
// fails); esz coverage [N]u8 + [N]i32; base coverage struct-field array (N_DOT),
// via-*struct-param, and bare-local (N_IDENT). READ rows lock the reslice-consume
// companion (#5, already clean).
package slice_copy_assign_test;
type buffer = struct { buf: [8]u8, end: i32 };
type box = struct { o: [8]i32 };
fn wr(s: *buffer, src: []u8) void = { s.buf[1:4] = src; };
@test fn w_u8_lo0() void = {
// struct-field [N]u8 base (the appendlit shape).
let s: buffer;
let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8;
s.buf[0:3] = bs[0:3];
assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3]
+ s.buf[4] + s.buf[5]): i32 == 60);
};
@test fn w_u8_lo2() void = {
let s: buffer;
let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8;
s.buf[2:5] = bs[0:3];
assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3]
+ s.buf[4] + s.buf[5]): i32 == 60);
};
@test fn w_u8_single() void = {
let s: buffer;
let bs: [1]u8; bs[0] = 42u8;
s.buf[3:4] = bs[0:1];
assert((s.buf[2] + s.buf[3] + s.buf[4]): i32 == 42);
};
@test fn w_empty() void = {
// hi==lo — count=0; the loop's CMPQ $0 guard must JLE-exit before the first
// MOVB (an off-by-one underflow would clobber buf[lo] and run away).
let s: buffer; s.buf[2] = 5u8; s.buf[3] = 7u8;
let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8;
s.buf[2:2] = bs[0:0];
assert((s.buf[1] + s.buf[2] + s.buf[3] + s.buf[4]): i32 == 12);
};
@test fn w_i32() void = {
// esz=4 — [N]i32 field strides by the element width, not 1.
let s: box;
let bs: [2]i32; bs[0] = 40; bs[1] = 88;
s.o[1:3] = bs[0:2];
assert((s.o[0] + s.o[1] + s.o[2] + s.o[3]): i32 == 128);
};
@test fn w_viaptr() void = {
// the field write through a *struct param (appendlit's `buf: *buffer`).
let s: buffer;
let bs: [3]u8; bs[0] = 11u8; bs[1] = 22u8; bs[2] = 33u8;
wr(&s, bs[0:3]);
assert((s.buf[0] + s.buf[1] + s.buf[2] + s.buf[3]
+ s.buf[4]): i32 == 66);
};
@test fn w_local() void = {
// N_IDENT base — a bare-local array place.
let a: [8]u8;
let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8;
a[2:5] = bs[0:3];
assert((a[0] + a[1] + a[2] + a[3] + a[4] + a[5]): i32 == 60);
};
@test fn rd_reslice() void = {
// READ twin (#5, already clean) — reslice the field array with a runtime hi
// bound, consume as []u8.
let s: buffer;
let bs: [3]u8; bs[0] = 10u8; bs[1] = 20u8; bs[2] = 30u8;
s.buf[0:3] = bs[0:3];
s.end = 3;
let v: []u8 = s.buf[0:s.end];
// 10+20+30 + 3*100 == 360 (the .c twin's want=104 was 360 & 0xFF, an
// exit-code truncation; the @test asserts the true value).
assert(v[0]: i32 + v[1]: i32 + v[2]: i32 + (v.len: i32) * 100 == 360);
};
@test fn rd_len() void = {
let s: buffer;
s.end = 5;
let v: []u8 = s.buf[0:s.end];
assert(v.len: i32 == 5);
};

View File

@@ -0,0 +1,41 @@
// try_str_unwrap_test — the `!` unwrap of a str-success tagged union must
// shuffle the str header (DX,CX,R8) → (AX,BX,CX) for ANY operand shape and ANY
// variant order, keyed off the STAMPED operand type, migrated from
// test/wcc/989_trystr_run.c (#16). The wwstage cgtryprop/cgtryunw computed
// succisstr only for an N_CALL operand (name-keyed fnretlookupmod on the callee
// leaf) and tested the FIRST variant, so an ident-source unwrap and an
// error-first union both dropped the `MOVQ CX,BX / MOVQ R8,CX` shuffle and kept
// a stale len/cap; the fix reads the stamped operand's success-variant type via
// successvariant()+typeisstr(). A 40-byte `junk` str precedes each unwrap so a
// dropped shuffle leaks junk's len (40) into s.len: pre-fix ident_source ran
// ww=40 vs cs=2 (silent overrun). cs==ww was byte-id-blind on this shape.
package try_str_unwrap_test;
type e = !i32;
fn mk_se() (str | e) = { return "hi"; };
fn mk2_es() (e | str) = { return "abc"; };
fn mk_se4() (str | e) = { return "wxyz"; };
@test fn ident_source() void = {
// ident-source unwrap (`let r = mk(); r!`) — the dropped-shuffle case.
let r: (str | e) = mk_se();
let junk: str = "0123456789012345678901234567890123456789";
let s: str = r!;
assert(s.len: i32 == 2);
};
@test fn errfirst_call() void = {
// error-first union (e | str): the success variant is NOT the first.
let junk: str = "0123456789012345678901234567890123456789";
let s3: str = mk2_es()!;
assert(s3.len: i32 == 3);
};
@test fn succfirst_call() void = {
// success-first call (str | e) — the control the pre-fix path got right.
let junk: str = "0123456789012345678901234567890123456789";
let s: str = mk_se4()!;
assert(s.len: i32 == 4);
};

View File

@@ -0,0 +1,64 @@
// tuple_elem_slice_len_test — `len(t.N)` where t is a tuple and t.N is a
// slice/str-typed element, migrated from test/wcc/903_tuple_elem_slice_len_run.c
// (#235). The len() builtin special-cased only a PLAIN N_IDENT slice operand
// and an array operand; every other shape fell back to a bare `cgexpr(operand)`,
// which for a slice leaves AX=.ptr. A tuple-element read (`t.N`) loads only
// AX=.ptr, so `len(t.N)` returned the slice's .ptr word AS the length — a SILENT
// miscompile, gate-blind because the bootstrap never does len() on a
// slice-typed tuple element. The fix (both stages, byte-id) loads the element's
// .len word directly at BP + tuple_off + element_off + 8. Every element has a
// DISTINCT length so a dropped/wrong field is caught. Tuples are slice/str-ONLY
// here — a leading scalar element exercises a separate mixed-tuple sret
// divergence (filed apart, out of scope).
package tuple_elem_slice_len_test;
fn mk2() ([]u8, []u8) = {
let a: []u8; a.len = 3; a.cap = 7;
let b: []u8; b.len = 5; b.cap = 9;
return (a, b);
};
fn mk3() ([]u8, []u8, []u8) = {
let a: []u8; a.len = 3; a.cap = 7;
let b: []u8; b.len = 5; b.cap = 9;
let c: []u8; c.len = 11; c.cap = 13;
return (a, b, c);
};
fn mksb() (str, []u8) = {
let s: str = "abcd";
let b: []u8; b.len = 6; b.cap = 8;
return (s, b);
};
fn mkbs() ([]u8, str) = {
let b: []u8; b.len = 7; b.cap = 9;
let s: str = "hi";
return (b, s);
};
@test fn two_slice() void = {
let t: ([]u8, []u8) = mk2();
assert(len(t.0): i32 == 3);
assert(len(t.1): i32 == 5);
};
@test fn three_slice() void = {
let t: ([]u8, []u8, []u8) = mk3();
assert(len(t.0): i32 == 3);
assert(len(t.1): i32 == 5);
assert(len(t.2): i32 == 11);
};
@test fn str_slice() void = {
let t: (str, []u8) = mksb();
assert(len(t.0): i32 == 4);
assert(len(t.1): i32 == 6);
};
@test fn slice_str() void = {
let t: ([]u8, str) = mkbs();
assert(len(t.0): i32 == 7);
assert(len(t.1): i32 == 2);
};