wcc: reject transitive nested-tagged widen at type_assignable (#199 α)
cgen has no wrapped-slot layout — the tagged-union slot is universally [tag:8B][payload:up_to_24B], single level. The recursive walk admitted let r: (size|io.eof|io.error) = u for u: io.underread (transitively in io.error.params); cg_tag_for_variant + taggedvariantindext don't recurse, returned -1, defaulted to tag=0, and the slot read back as variant 0 = size at runtime. Restores SSoT inside the checker pair: is / as / match variant lookup is already non-recursive (#198 sibling), and the LET-init / return / assign arms now agree. Aligns DOWN to the leaner side (rule-10 stage symmetry). ww-stricter than Hare; harec keeps the drill at ref/harec/src/types.c:702-739 (#199b is the deferred wrapped-slot layout port). Pre-flight audit (drew mandate): zero transitive-widen sites in lib/ + selfhost/ + cmd/ + examples/. No wrapper-tagged variant (io.error, strconv.error, fmt.field) is used as a variant of a wider union anywhere in bootstrap. Mechanical fix. Escape hatch for callers: spread (...wrapper) inlines the wrapper's flat variants into the parent set at parse time. Wwstage's gate additionally preserves the recursive drill on op == TK_ELLIPSIS because wwstage stays AST-keyed (cstage flattens at resolve_type). 771_widen_transitive: 5 rows (reject_transitive_widen, spread_alt_widen, direct_flat_variant, branched_callee_widen, wrapper_typed_widen). Row 2 is CS-only — wwstage's is / match on spread-expanded variants is open-bug #190/#198.
This commit is contained in:
@@ -13080,21 +13080,32 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
return true;
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
// Recursive isassignable mirrors cstage type_assignable
|
||||
// (cmd/wcc/type.c:298-299) and harec tagged_select_subtype's
|
||||
// recursive type_is_assignable call (ref/harec/src/types.c:702-739,
|
||||
// :718; invoked from the TAGGED arm at :1110-1112). #39 cascade:
|
||||
// the prior typeeqast-only walk rejected widenings that aren't
|
||||
// strict surface-eq (NAMED-aliased variants, nested tagged inside
|
||||
// a variant, concrete → variant after the wrap-induced exprtype
|
||||
// reshape). #55 surface-nominal fast path is preserved by the
|
||||
// recursive call's leading typeeqast (line 2257). #57 bare-vs-
|
||||
// qualified TNAME residual unchanged.
|
||||
// #199(α): direct variant only — no transitive drill into a
|
||||
// NAMED-tagged wrapper variant. Cgen has no wrapped-slot layout
|
||||
// (taggedvariantindext returns -1 → tag=0 silent miscompile on
|
||||
// io.underread → (size|io.eof|io.error)). ww-stricter than Hare;
|
||||
// harec keeps the drill at types.c:702-739 (#199b deferred port).
|
||||
// Restores SSoT with `is`/`as` non-recursive lookup (#198 sibling).
|
||||
// Callers compose `let inner: Wrapper = sub; let r: parent = inner;`.
|
||||
if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) {
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
// Spread `...wrapper` keeps the recursive drill: the
|
||||
// wrapper's flat variants are intentionally inlined into
|
||||
// the parent set, and AST-level params haven't been
|
||||
// expanded yet (cstage flattens at resolve_type; wwstage
|
||||
// stays AST-keyed). Plain wrapper variant gets the
|
||||
// direct-only gate.
|
||||
let vspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
let vu: *node = resolvealias(c, unwrapbang(v));
|
||||
let vtagged: bool = false;
|
||||
if (vu != nil) { if (vu.kind == nkind.N_TTAGGED) { vtagged = true; }; };
|
||||
if (vtagged && !vspread) {
|
||||
if (typeeqast(v, src)) { return true; };
|
||||
} else {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
|
||||
@@ -3050,21 +3050,32 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
return true;
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
// Recursive isassignable mirrors cstage type_assignable
|
||||
// (cmd/wcc/type.c:298-299) and harec tagged_select_subtype's
|
||||
// recursive type_is_assignable call (ref/harec/src/types.c:702-739,
|
||||
// :718; invoked from the TAGGED arm at :1110-1112). #39 cascade:
|
||||
// the prior typeeqast-only walk rejected widenings that aren't
|
||||
// strict surface-eq (NAMED-aliased variants, nested tagged inside
|
||||
// a variant, concrete → variant after the wrap-induced exprtype
|
||||
// reshape). #55 surface-nominal fast path is preserved by the
|
||||
// recursive call's leading typeeqast (line 2257). #57 bare-vs-
|
||||
// qualified TNAME residual unchanged.
|
||||
// #199(α): direct variant only — no transitive drill into a
|
||||
// NAMED-tagged wrapper variant. Cgen has no wrapped-slot layout
|
||||
// (taggedvariantindext returns -1 → tag=0 silent miscompile on
|
||||
// io.underread → (size|io.eof|io.error)). ww-stricter than Hare;
|
||||
// harec keeps the drill at types.c:702-739 (#199b deferred port).
|
||||
// Restores SSoT with `is`/`as` non-recursive lookup (#198 sibling).
|
||||
// Callers compose `let inner: Wrapper = sub; let r: parent = inner;`.
|
||||
if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) {
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
// Spread `...wrapper` keeps the recursive drill: the
|
||||
// wrapper's flat variants are intentionally inlined into
|
||||
// the parent set, and AST-level params haven't been
|
||||
// expanded yet (cstage flattens at resolve_type; wwstage
|
||||
// stays AST-keyed). Plain wrapper variant gets the
|
||||
// direct-only gate.
|
||||
let vspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
let vu: *node = resolvealias(c, unwrapbang(v));
|
||||
let vtagged: bool = false;
|
||||
if (vu != nil) { if (vu.kind == nkind.N_TTAGGED) { vtagged = true; }; };
|
||||
if (vtagged && !vspread) {
|
||||
if (typeeqast(v, src)) { return true; };
|
||||
} else {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
|
||||
@@ -13080,21 +13080,32 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
return true;
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
// Recursive isassignable mirrors cstage type_assignable
|
||||
// (cmd/wcc/type.c:298-299) and harec tagged_select_subtype's
|
||||
// recursive type_is_assignable call (ref/harec/src/types.c:702-739,
|
||||
// :718; invoked from the TAGGED arm at :1110-1112). #39 cascade:
|
||||
// the prior typeeqast-only walk rejected widenings that aren't
|
||||
// strict surface-eq (NAMED-aliased variants, nested tagged inside
|
||||
// a variant, concrete → variant after the wrap-induced exprtype
|
||||
// reshape). #55 surface-nominal fast path is preserved by the
|
||||
// recursive call's leading typeeqast (line 2257). #57 bare-vs-
|
||||
// qualified TNAME residual unchanged.
|
||||
// #199(α): direct variant only — no transitive drill into a
|
||||
// NAMED-tagged wrapper variant. Cgen has no wrapped-slot layout
|
||||
// (taggedvariantindext returns -1 → tag=0 silent miscompile on
|
||||
// io.underread → (size|io.eof|io.error)). ww-stricter than Hare;
|
||||
// harec keeps the drill at types.c:702-739 (#199b deferred port).
|
||||
// Restores SSoT with `is`/`as` non-recursive lookup (#198 sibling).
|
||||
// Callers compose `let inner: Wrapper = sub; let r: parent = inner;`.
|
||||
if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) {
|
||||
let v: *node = du.list;
|
||||
for (v != nil) {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
// Spread `...wrapper` keeps the recursive drill: the
|
||||
// wrapper's flat variants are intentionally inlined into
|
||||
// the parent set, and AST-level params haven't been
|
||||
// expanded yet (cstage flattens at resolve_type; wwstage
|
||||
// stays AST-keyed). Plain wrapper variant gets the
|
||||
// direct-only gate.
|
||||
let vspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
let vu: *node = resolvealias(c, unwrapbang(v));
|
||||
let vtagged: bool = false;
|
||||
if (vu != nil) { if (vu.kind == nkind.N_TTAGGED) { vtagged = true; }; };
|
||||
if (vtagged && !vspread) {
|
||||
if (typeeqast(v, src)) { return true; };
|
||||
} else {
|
||||
let innerconf: bool = false;
|
||||
if (isassignable(c, v, src, &innerconf)) { return true; };
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user