// arrvar_union_slice_test — #17: an array VARIABLE coerced into the SLICE // success variant of a tagged union (`let a:[N]i32; ... ([]i32|e)`) silently // dropped .len/.cap (and .ptr) — a both-stage miscompile the byte-id gate is // BLIND to (cstage and wwstage agreed and were BOTH wrong). // // Root cause was in the CHECKER, not cgen: desugar_arrayslice — the single // #258 [N]T->[]T borrow lowering that rewrites the array source to a full // slice `a[0:len(a)]` so the existing slice cgen builds the {ptr,len,cap} // header — no-op'd when the borrow target chased to a tagged union (it gated // `du->kind == TY_SLICE` only). The bare array source then reached cgen's // scalar union-widen arm, which loaded the array's first 8 bytes as a single // payload word and zeroed len/cap. The fix chases a TY_TAGGED dst to its slice // success variant before lowering (the exact #13 reject_arrlit_borrow chase, // applied to a LEGAL array-variable borrow rather than a reject) — closing the // class by construction at the one shared choke-point, so RETURN / LET-init / // ASSIGN / CALL-ARG all lower identically. // // Both stages emit identically-wrong asm pre-fix, so byte-id is blind — these // VALUE asserts are the sole tooth: len, cap (the dropped words) AND first/last // elements (proving .ptr too). Reverting either stage's desugar chase reverts // to the scalar-widen drop and reddens. // // DISTINCT from #13/#18: those REJECT an illegal array-LITERAL temp borrowed // into a union slice variant (a temp has no outliving backing). #17 is the // opposite — an array VARIABLE HAS storage, so the borrow is legal and must // WORK. A typed array literal here still loud-rejects (reject_arrlit_borrow // runs first); only the variable desugars. // // multi_variant_form pins the variant SELECTION: `[4]i32` into a MULTI-slice // union ([]u8|[]i32|e) must chase to the []i32 member by ELEMENT equality, not // merely the first slice ([]u8). A naive first-N_TSLICE chase would pick []u8, // fail the main desugar gate's element-eq guard, and no-op straight back to the // len/cap drop. Faithful to harec tagged_select_subtype (types.c:702 — a UNIQUE // assignable member; an array is element-eq to at most one slice variant). // // Inline @test fns, not a row-table: the cases vary in the COERCION-CONTEXT // place shape (return / let-init / assign / call-arg), not in data values over // one operation, so a row-array `[](in,exp){}` can't express them — and that // form is itself blocked by cgen #111. Mirrors global_chained_unwrap_test's // #16 pins. package arrvar_union_slice_test; type e = !i32; fn mk() ([]i32 | e) = { // RETURN: array var -> union slice let a: [4]i32 = [11, 22, 33, 44]; return a; }; fn use_slice(s: ([]i32 | e)) i32 = { // receives the CALL-ARG widen let v = s!; return len(v): i32 + v.cap: i32 + v[0] + v[3]; }; @test fn ret_form() void = { let s = mk()!; assert(len(s) == 4); // .len tooth assert(s.cap == 4); // .cap tooth assert(s[0] == 11); // .ptr teeth assert(s[1] == 22); assert(s[2] == 33); assert(s[3] == 44); }; @test fn let_form() void = { // LET-init: `let r:([]i32|e) = a;` let a: [3]i32 = [7, 8, 9]; let r: ([]i32 | e) = a; let s = r!; assert(len(s) == 3); assert(s.cap == 3); assert(s[0] == 7); assert(s[2] == 9); }; @test fn assign_form() void = { // ASSIGN: `r = a;` into a union let a0: [2]i32 = [1, 1]; let r: ([]i32 | e) = a0; let a: [2]i32 = [5, 6]; r = a; let s = r!; assert(len(s) == 2); assert(s.cap == 2); assert(s[0] == 5); assert(s[1] == 6); }; @test fn callarg_form() void = { // CALL-ARG: `f(a)` where f takes union let a: [4]i32 = [11, 22, 33, 44]; assert(use_slice(a) == 4 + 4 + 11 + 44); }; fn mk_multi() ([]u8 | []i32 | e) = { // MULTI-slice union: must pick []i32, not leading []u8 let a: [4]i32 = [11, 22, 33, 44]; return a; }; @test fn multi_variant_form() void = { // element-eq chase selects the RIGHT slice variant let got: i32 = 0; match (mk_multi()) { case let s: []i32 => got = len(s): i32 + s.cap: i32 + s[0] + s[3]; case let b: []u8 => got = -1; // wrong: chase mis-picked the leading slice case let er: e => got = -2; // wrong: chase fell through to the no-op drop }; assert(got == 4 + 4 + 11 + 44); };