diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 87112654..0c7457bc 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -1192,6 +1192,22 @@ reject_arrlit_borrow(Checker *c, Type *dst, Node *expr) { if (expr == NULL || expr->kind != N_ARRLIT) return 0; Type *du = type_chase_named(dst); + /* #13: the borrow target may be a SLICE success variant of a tagged- + * union return — the same no-outliving-backing dangle as a bare slice, + * but it slips the TY_SLICE gate (c->ret chases to TY_TAGGED). Chase to + * the assignable slice variant so the reject sees through the union. An + * array-typed variant is the separate #5/#60 reject upstream; full + * non-let support (an outliving backing) is #33. */ + if (du && du->kind == TY_TAGGED) { + for (Tparam *p = du->params; p; p = p->next) { + Type *pu = type_chase_named(p->type); + if (pu && pu->kind == TY_SLICE + && type_assignable(p->type, expr->type)) { + du = pu; + break; + } + } + } if (du == NULL || du->kind != TY_SLICE) return 0; err(c, expr->pos, "array literal cannot borrow as a slice here; " "bind it to a `let` first"); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 1c4ae9d0..5e2794ba 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -5607,6 +5607,23 @@ fn rejectarrlitborrow(c: *checker, dsttn: *syntax.node, val: *syntax.node) bool if (val.kind != syntax.nkind.N_ARRLIT) { return false; }; let du: *syntax.node = resolvealias(c, unwrapbang(dsttn)); if (du == nil) { return false; }; + // #13: the borrow target may be a SLICE success variant of a tagged- + // union return — same no-outliving-backing dangle, but it slips the + // N_TSLICE gate (the dst node chases to N_TTAGGED). Chase to a slice + // variant so the reject sees through the union. An N_ARRLIT can only + // target a slice variant; an array-typed variant is the #5/#60 reject + // upstream; full non-let support is #33. Twin of cstage #13 arm. + if (du.kind == syntax.nkind.N_TTAGGED) { + 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) { + du = vu; + break; + }; + v = v.next; + }; + }; if (du.kind != syntax.nkind.N_TSLICE) { return false; }; let m: str = "array literal cannot borrow as a slice here; bind it to a `let` first\n"; cerr(m); diff --git a/test/wcc/data/arrlit_slice_reject_union_ret/case.ww b/test/wcc/data/arrlit_slice_reject_union_ret/case.ww new file mode 100644 index 00000000..66cb750b --- /dev/null +++ b/test/wcc/data/arrlit_slice_reject_union_ret/case.ww @@ -0,0 +1,14 @@ +//ww:error "cannot borrow as a slice here" +// #13 carrier: an array LITERAL returned into a tagged-union SLICE success +// variant ([]i32 | e) has no outliving backing (the maker's frame dies) — the +// .ptr would dangle, the same #31/#33 no-backing reject as a bare-slice return. +// Both stages must REJECT: the borrow gate now sees THROUGH the union to its +// []i32 success variant (the silent all-zeros-header miscompile gap). Full +// non-let support (an outliving backing) is #33. +package main; +type e = !i32; +fn mk() ([]i32 | e) = { return [10i32, 20i32, 30i32]; }; +export fn main() i32 = { + let s: []i32 = mk()!; + return s[0]; +}; diff --git a/test/wcc/data/arrlit_slice_union_ok/case.ww b/test/wcc/data/arrlit_slice_union_ok/case.ww new file mode 100644 index 00000000..3298f184 --- /dev/null +++ b/test/wcc/data/arrlit_slice_union_ok/case.ww @@ -0,0 +1,14 @@ +//ww:run-exit 10 +// #13 over-reach guard: a REAL slice returned into the same tagged-union +// success variant ([]i32 | e) has a live backing (the caller's array `a`), so +// both stages must still ACCEPT and build the correct header (tag=0, byte-id). +// Discriminates the array-LITERAL reject (sibling carrier) from a blanket +// union-slice-return reject — proves the #13 fix is surgical. Returns s[0]=10. +package main; +type e = !i32; +fn mk(src: []i32) ([]i32 | e) = { return src; }; +export fn main() i32 = { + let a: [3]i32 = [10i32, 20i32, 30i32]; + let s: []i32 = mk(a)!; + return s[0]; +};