diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index f74a6470..63a98e77 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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) { diff --git a/test/wcc/data/arrlit_slice_reject_union_untyped/case.ww b/test/wcc/data/arrlit_slice_reject_union_untyped/case.ww new file mode 100644 index 00000000..e7172410 --- /dev/null +++ b/test/wcc/data/arrlit_slice_reject_union_untyped/case.ww @@ -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]; +};