wcc/check: A7 honest-floor tagged-subset reject closes wide→narrow miscompile (wwstage)
wwstage's tagged→tagged subset-assign arm accepted all (lenient escape), silently miscompiling implicit wide→narrow: a (int|bool|str) holding a str, assigned to (int|bool), ran the int arm and read the str pointer as int. cstage rejects loud; this escape was the lone divergence. Replace the escape with cstage's subset walk (src ⊆ dst): a src variant is covered iff typeeqast matches (structural — []u8/nested/primitives) OR both are N_TNAME with equal leaf names. The leaf bridge covers cross-module forwards where a callee's bare inline-union variant (utf8's `done`) meets the consumer's qualified `utf8.done` — raw typeeqast can't, and the escape was masking it for every forward. Mirrors casecovers (check.ww:4136). cstage untouched (align-up); spread-bearing unions keep the escape (#199b orthogonal). Honest floor: leaf-only defers true module identity to #10 — a callee's defining module for an inline-union return is unrecoverable at check-time (the call node is gone; #211 fnretlookupmod is cgen-only). Retained divergence = cross-module same-leaf-collision over-accept (absent from bootstrap), documented at the site and filed as #10-A7 / census cat-A. Test 989_tagged_subset_reject is table-driven with composition-discriminating rows: []u8 subset (typeeqast-only), bare↔qualified xmod forward (leaf-only), xmod named genuine-absence (reject). 352 green; w6c unchanged, w6c_ww 4b4496b8→4b316f01.
This commit is contained in:
@@ -14243,7 +14243,57 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
*confident = false;
|
||||
// Spread `...` member on either side: keep the lenient escape.
|
||||
// cstage flattens spreads at resolve_type so its subset loop
|
||||
// never sees one; wwstage stays AST-keyed (#115, check.ww:921),
|
||||
// so a TK_ELLIPSIS variant can reach here. Routing it through
|
||||
// the strict typeeqast subset loop would over-reject a valid
|
||||
// spread-widen cstage accepts → new cs≠ww divergence. Spread
|
||||
// decl-form layout is #199b, deferred.
|
||||
let hasspread: bool = false;
|
||||
for (let p: *node = du.list; p != nil; p = p.next) {
|
||||
if (p.op == tkind.TK_ELLIPSIS) { hasspread = true; };
|
||||
};
|
||||
for (let p: *node = su.list; p != nil; p = p.next) {
|
||||
if (p.op == tkind.TK_ELLIPSIS) { hasspread = true; };
|
||||
};
|
||||
if (hasspread) {
|
||||
*confident = false;
|
||||
return true;
|
||||
};
|
||||
// Structural subset loop — wwstage was align-DOWN-missing this;
|
||||
// cstage type.c type_assignable tagged→tagged arm (type.c:360-367).
|
||||
// Every src variant must appear in dst, else a loud reject; a
|
||||
// genuine subset accepts.
|
||||
//
|
||||
// Per-variant cover is the HONEST FLOOR (drew ruling A): typeeqast
|
||||
// FIRST for the exact / structural variants (e.g. []u8 slices,
|
||||
// nested unions — bufio scanbytes/scanline forward `[]u8`), THEN a
|
||||
// LEAF-ONLY name bridge (qualleaf, module IGNORED) for the bare-vs-
|
||||
// qualified spelling mix typeeqast cannot span. A callee returning
|
||||
// an INLINE union spells its variants BARE (utf8.next:
|
||||
// (rune|done|more|invalid)) while the consumer annotates them
|
||||
// QUALIFIED (utf8.done); the module qualifier is unrecoverable for
|
||||
// an inline-union return, so leaf alone decides. Mirrors casecovers'
|
||||
// typeeqast-then-leaf composition (check.ww:4136). qualmod is NOT
|
||||
// compared (cf casevariantpairmatch): module identity is the #10
|
||||
// gap. Sound for the bootstrap — no two of its variants share a leaf
|
||||
// (drew); the cross-module same-leaf collision (a.foo vs b.foo) is a
|
||||
// known over-accept deferred to #10 / filed in the #4 census.
|
||||
for (let sp: *node = su.list; sp != nil; sp = sp.next) {
|
||||
let ok: bool = false;
|
||||
for (let dp: *node = du.list; dp != nil; dp = dp.next) {
|
||||
if (typeeqast(c, dp, sp)) { ok = true; break; };
|
||||
let su2: *node = unwrapbang(sp);
|
||||
let du2: *node = unwrapbang(dp);
|
||||
if (su2 != nil && du2 != nil &&
|
||||
su2.kind == nkind.N_TNAME && du2.kind == nkind.N_TNAME &&
|
||||
streq(qualleaf(su2.str), qualleaf(du2.str))) {
|
||||
ok = true; break;
|
||||
};
|
||||
};
|
||||
if (!ok) { return false; };
|
||||
};
|
||||
return true;
|
||||
};
|
||||
// tagged → non-tagged: requires `?` / `!` / match to project a
|
||||
|
||||
Reference in New Issue
Block a user