From e6621565743b14e03e39e87e0f00e4fe29285275 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 22 May 2026 02:37:00 +0900 Subject: [PATCH] =?UTF-8?q?selfhost/cmd/wcc:=20isassignable=20arm=206=20?= =?UTF-8?q?=E2=86=92=20recursive=20(cascade=20Commit=201,=20#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The concrete→tagged arm walked variants with typeeqast-only, rejecting widenings that aren't strict surface-eq: NAMED-aliased variants, nested tagged inside a variant, and concrete → variant after the wrap-induced exprtype reshape that Commit 3 (#41, variadic wrap re-land) introduces. Replace the walk with a recursive isassignable call per variant, mirroring harec tagged_select_subtype (ref/harec/src/types.c:702-739, recursive type_is_assignable at :718; invoked from the TAGGED arm at :1110-1112) and the byte-id cstage precedent at cmd/wcc/type.c:298-299. The recursive call's leading typeeqast (check.ww:2257) preserves the #55 surface-nominal fast path; the #57 bare-vs-qualified TNAME residual is unchanged. No new test: the new widen path is dormant pre-Commit-3; existing tagged-union tests cover the surface-eq fast path. Commit 3's fmttest exercise will exercise the recursive widen for free. Closes 39 cascade errors that fire once #41 lands. --- selfhost/cmd/w6c/main.combined.ww | 33 ++++++++++------------------ selfhost/cmd/wcc/check.ww | 33 ++++++++++------------------ selfhost/cmd/wwdump/main.combined.ww | 33 ++++++++++------------------ 3 files changed, 36 insertions(+), 63 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index d51948d5..e871915f 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -9275,30 +9275,21 @@ 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. if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) { - // #55: nominal-first compare. Cstage variant_match - // (cmd/wcc/check.c:90-100) takes NAMED types as - // pointer-identical, so two `err`s match before bodies - // are resolved. Wwstage's typeeqast already does - // string-nominal on N_TNAME, but pre-fix this branch - // resolvealias-d both v and src to their bodies (e.g. - // N_TSTRUCT), and typeeqast's conservative struct arm - // returned false — `return e;` inside `fn f() (void | err)` - // got flagged. Compare surface forms first; fall through - // to resolved compare only when the surface mismatches - // (covers structurally-anonymous variant cases that - // resolvealias actually disambiguates). Residual: bare-vs- - // qualified TNAME (`(void | modM.err)` variant vs bare `err` - // inside modM) still misses both arms — filed as #57. - let srcraw: *node = unwrapbang(src); let v: *node = du.list; for (v != nil) { - let vraw: *node = unwrapbang(v); - if (typeeqast(vraw, srcraw)) { return true; }; - let vu: *node = resolvealias(c, vraw); - if (vu != nil) { - if (typeeqast(vu, su)) { return true; }; - }; + let innerconf: bool = false; + if (isassignable(c, v, src, &innerconf)) { return true; }; v = v.next; }; return false; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index cf1e2e94..c0d367df 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -2311,30 +2311,21 @@ 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. if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) { - // #55: nominal-first compare. Cstage variant_match - // (cmd/wcc/check.c:90-100) takes NAMED types as - // pointer-identical, so two `err`s match before bodies - // are resolved. Wwstage's typeeqast already does - // string-nominal on N_TNAME, but pre-fix this branch - // resolvealias-d both v and src to their bodies (e.g. - // N_TSTRUCT), and typeeqast's conservative struct arm - // returned false — `return e;` inside `fn f() (void | err)` - // got flagged. Compare surface forms first; fall through - // to resolved compare only when the surface mismatches - // (covers structurally-anonymous variant cases that - // resolvealias actually disambiguates). Residual: bare-vs- - // qualified TNAME (`(void | modM.err)` variant vs bare `err` - // inside modM) still misses both arms — filed as #57. - let srcraw: *node = unwrapbang(src); let v: *node = du.list; for (v != nil) { - let vraw: *node = unwrapbang(v); - if (typeeqast(vraw, srcraw)) { return true; }; - let vu: *node = resolvealias(c, vraw); - if (vu != nil) { - if (typeeqast(vu, su)) { return true; }; - }; + let innerconf: bool = false; + if (isassignable(c, v, src, &innerconf)) { return true; }; v = v.next; }; return false; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 6cedc0eb..74c625c4 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -9275,30 +9275,21 @@ 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. if (du.kind == nkind.N_TTAGGED && su.kind != nkind.N_TTAGGED) { - // #55: nominal-first compare. Cstage variant_match - // (cmd/wcc/check.c:90-100) takes NAMED types as - // pointer-identical, so two `err`s match before bodies - // are resolved. Wwstage's typeeqast already does - // string-nominal on N_TNAME, but pre-fix this branch - // resolvealias-d both v and src to their bodies (e.g. - // N_TSTRUCT), and typeeqast's conservative struct arm - // returned false — `return e;` inside `fn f() (void | err)` - // got flagged. Compare surface forms first; fall through - // to resolved compare only when the surface mismatches - // (covers structurally-anonymous variant cases that - // resolvealias actually disambiguates). Residual: bare-vs- - // qualified TNAME (`(void | modM.err)` variant vs bare `err` - // inside modM) still misses both arms — filed as #57. - let srcraw: *node = unwrapbang(src); let v: *node = du.list; for (v != nil) { - let vraw: *node = unwrapbang(v); - if (typeeqast(vraw, srcraw)) { return true; }; - let vu: *node = resolvealias(c, vraw); - if (vu != nil) { - if (typeeqast(vu, su)) { return true; }; - }; + let innerconf: bool = false; + if (isassignable(c, v, src, &innerconf)) { return true; }; v = v.next; }; return false;