check: lower an array-variable borrow into a union slice success variant (#17)
This commit is contained in:
@@ -1221,6 +1221,25 @@ desugar_arrayslice(Checker *c, Type *dst, Node *expr)
|
||||
return;
|
||||
Type *du = type_chase_named(dst);
|
||||
Type *su = type_chase_named(expr->type);
|
||||
/* #17: the borrow target may be the SLICE success variant of a tagged
|
||||
* union (`return/assign/let/f(arr)` into `([]T|e)`). Chase du to that
|
||||
* variant so an array VARIABLE lowers to a full slice exactly as a
|
||||
* bare []T dst does — the existing slice cgen then builds the full
|
||||
* {ptr,len,cap} header and the union widen's slice arm wraps it,
|
||||
* closing the silent len/cap drop. Mirrors #13's reject_arrlit_borrow
|
||||
* TY_TAGGED chase (an array LITERAL has no outliving backing and is
|
||||
* rejected there first; a variable has storage, so this borrow is
|
||||
* legal). An array-typed variant is the separate #5/#60 reject. */
|
||||
if (du && du->kind == TY_TAGGED && su && su->kind == TY_ARRAY) {
|
||||
for (Tparam *p = du->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
if (pu && pu->kind == TY_SLICE
|
||||
&& type_eq(pu->sub, su->sub)) {
|
||||
du = pu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (du == NULL || su == NULL ||
|
||||
du->kind != TY_SLICE || su->kind != TY_ARRAY)
|
||||
return;
|
||||
|
||||
@@ -5639,6 +5639,25 @@ fn desugararrayslice(c: *checker, dsttn: *syntax.node, srctn: *syntax.node, val:
|
||||
let su: *syntax.node = resolvealias(c, unwrapbang(srctn));
|
||||
if (du == nil) { return val; };
|
||||
if (su == nil) { return val; };
|
||||
// #17: the borrow target may be the SLICE success variant of a tagged
|
||||
// union (`return/assign/let/f(arr)` into `([]T|e)`). Chase du to that
|
||||
// variant so an array VARIABLE lowers to a full slice exactly as a bare
|
||||
// []T dst does — the existing slice cgen then builds the full
|
||||
// {ptr,len,cap} header and the union widen's slice arm wraps it, closing
|
||||
// the silent len/cap drop. Mirrors #13's rejectarrlitborrow N_TTAGGED
|
||||
// chase (an array LITERAL has no outliving backing and is rejected there
|
||||
// first; a variable has storage, so this borrow is legal).
|
||||
if (du.kind == syntax.nkind.N_TTAGGED && su.kind == syntax.nkind.N_TARRAY) {
|
||||
let v: *syntax.node = du.list;
|
||||
for (v != nil) {
|
||||
let vu: *syntax.node = resolvealias(c, unwrapbang(v));
|
||||
if (vu != nil && vu.kind == syntax.nkind.N_TSLICE && typeeqast(c, vu.lhs, su.lhs)) {
|
||||
du = vu;
|
||||
break;
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
};
|
||||
if (du.kind != syntax.nkind.N_TSLICE) { return val; };
|
||||
if (su.kind != syntax.nkind.N_TARRAY) { return val; };
|
||||
if (!typeeqast(c, du.lhs, su.lhs)) { return val; };
|
||||
|
||||
109
test/lang/arrvar_union_slice_test.ww
Normal file
109
test/lang/arrvar_union_slice_test.ww
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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);
|
||||
};
|
||||
Reference in New Issue
Block a user