check: reject an untyped array-literal borrow into a union slice variant (#18)

This commit is contained in:
2026-06-28 11:27:24 +09:00
parent c6cdd8c2d6
commit d0ce55cab8
2 changed files with 55 additions and 0 deletions

View File

@@ -4856,6 +4856,26 @@ fn isassignable(c: *checker, dst: *syntax.node, src: *syntax.node, confident: *b
// qualleaf bridge). Pre-c3 the collision was ALSO accepted (call-arg
// ran no check; let/return short-circuited on the scalar variant) — c3
// is NEUTRAL on it.
// #18: an ARRAY src reaching here means NO slice/array variant
// matched by element type — the loop above confident-rejects a
// [N]T-vs-[]U element mismatch and returns true on an exact match,
// so an array surviving to here matches nothing. An array's only
// legal tagged path is an exact-element slice variant (#17 desugar)
// or an array variant (#5/#60), both decided confidently in the
// loop. The shape-lenient leg below exists for nominal-lossy cross-
// module ptr/struct identity (#10/#66/#37); arrays never have it
// (element identity is structural, fully AST-known). So a [N]T here
// is a CONFIDENT reject — mirror cstage type_assignable's concrete→
// tagged arm returning 0 (cmd/wcc/type.c:344) and harec
// tagged_select_subtype (no unique assignable member, ref/harec/src/
// types.c:702 + the STORAGE_SLICE to_secondary==from_secondary gate
// :1097). Without this an untyped-int arrlit return (`[10,20,30]`
// defaults to [3]int) shape-matched the `e` (i32) variant (both
// tagshape 0) → conf=false → checkretassign bailed before
// rejectarrlitborrow = silent accept (wwstage-only, cs≠ww).
if (su.kind == syntax.nkind.N_TARRAY) {
return false; // *confident already true on this path
};
if (su.kind != syntax.nkind.N_TNAME) {
let ss: i32 = tagshape(su);
let sp: *syntax.node = du.list;
@@ -5613,6 +5633,20 @@ fn rejectarrlitborrow(c: *checker, dsttn: *syntax.node, val: *syntax.node) bool
// 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.
//
// #20 RETAINED DIVERGENCE (benign, rule-7 tracked, NEVER silent): this
// chase is UNGATED — it picks the first slice variant whatever the
// arrlit's element type. cstage's reject_arrlit_borrow gates the same
// chase on type_assignable (cmd/wcc/check.c reject_arrlit_borrow), so
// for an UNTYPED arrlit whose element matches NO variant (#18: [3]int
// into ([]i32|e)) cstage DECLINES the chase → rejects purely at the
// assignability layer ("not assignable"). wwstage emits "not assignable"
// too (errnotassign, checkretassign) and THEN this extra "cannot borrow
// as a slice". Both stages reject the SAME SET — no decision/asm
// divergence (a reject emits no .s; rule-10 is asm-only). The exact-
// parity port (gate this chase on isassignable, mirroring cstage) is
// task #20; it touches this shared #13/#17/#18 chokepoint, so it is its
// own commit.
if (du.kind == syntax.nkind.N_TTAGGED) {
let v: *syntax.node = du.list;
for (v != nil) {

View File

@@ -0,0 +1,21 @@
//ww:error "not assignable"
// #18 carrier: an UNTYPED-int array literal returned into a tagged-union SLICE
// success variant ([]i32 | e). The literal defaults to [3]int, which is NOT
// assignable to any variant ([]i32 wants i32 elements; e is i32), so BOTH stages
// must REJECT at the assignability layer — harec tagged_select_subtype needs a
// UNIQUE assignable member and the STORAGE_SLICE arm requires element identity
// (ref/harec/src/types.c:702 + :1097). cstage rejects via type_assignable
// returning 0 (cmd/wcc/type.c:344); wwstage's isassignable previously shape-
// matched [3]int against the `e` (i32) scalar variant (both tagshape 0) and went
// LENIENT (confident=false), so checkretassign short-circuited before the borrow
// gate and SILENTLY ACCEPTED — a wwstage-only miscompile (cs≠ww). The fix makes
// an array src with no element-matching variant a CONFIDENT reject in isassignable.
// Sibling of #13 (the TYPED-literal leg, rejected by the borrow gate after it
// passes assignability) and distinct from #17 (a legal array VARIABLE coercion).
package main;
type e = !i32;
fn mk() ([]i32 | e) = { return [10, 20, 30]; };
export fn main() i32 = {
let s: []i32 = mk()!;
return s[0];
};