selfhost/cmd/wcc/check: nominal-first compare in tagged-variant inclusion
isassignable's tagged-variant inclusion was resolvealias-unwrapping both src and each variant before typeeqast. Two NAMED structs (e.g. `(void | err)` with src=`err`) both flattened to N_TSTRUCT and typeeqast's conservative struct branch returned false — false positive on the assignability. Cstage variant_match (cmd/wcc/check.c:90-100) compares TY_NAMED pointer-identically, so the nominal name short-circuits before any body inspection. Mirror: try typeeqast on unwrapbang'd src vs unwrapbang'd variant first (catches the N_TNAME nominal match), fall through to resolvealias + structural compare for anonymous- union variants only. Reviewer's negative probe (different types modA.err vs modB.err with same leaf name) still correctly rejects — the parser joins pkg.alias into one TNAME string, so modA.err ≠ modB.err at the nominal level. Bare-vs-qualified residual (cstage admits `(void | M.err)` ← bare `err` inside module M; wwstage still rejects) tracked as #57. Not hit by any current fixture; unblocks #50 (after #56) and #42. 131/131 + 4 lines of pre-existing pessimism cleared in selfhost/cmd/wcc/check.ww's own resolution.
This commit is contained in:
@@ -7867,9 +7867,26 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
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 vu: *node = resolvealias(c, unwrapbang(v));
|
||||
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; };
|
||||
};
|
||||
|
||||
@@ -873,9 +873,26 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
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 vu: *node = resolvealias(c, unwrapbang(v));
|
||||
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; };
|
||||
};
|
||||
|
||||
@@ -7867,9 +7867,26 @@ fn isassignable(c: *checker, dst: *node, src: *node, confident: *bool) bool = {
|
||||
};
|
||||
// Tagged-union variant inclusion: src is one of dst's variants.
|
||||
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 vu: *node = resolvealias(c, unwrapbang(v));
|
||||
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; };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user