diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index b9d1ba84..c1039f5e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; }; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 4005fe75..3ded9f30 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 28d502c3..5b0ae1b4 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; }; };