w6c+wwstage: tag a bare value widened into a NAMED-alias union variant via structural fallback (#15)

Variant selection (cg_tag_for_variant / flatvariantidxt) matched union variants by exact type only, so widening a bare value (e.g. *vtable) into a union with a NAMED ptr-alias variant (stream = *vtable, in handle = (file | stream)) found no match and the tag defaulted to 0 -- the wrong variant. In the compiler this hit emitbytes' io.write(&cgoutstream.vt) once io.write took a handle, writing the asm to a garbage fd -> empty .s -> w6c_ww miscompiled everything. Add a second selection pass: when the exact pass finds no variant, structurally compare the bare source against each NAMED-alias variant's unwrapped type; exact-match still wins in pass 1 (so a bare i64 stays the i64 variant, not oserror=!i64, which kept the os/errno union building). A >=2-structural-match collision guard (extending #218's) hard-errors LOUDLY on genuine nominal ambiguity (two ptr-aliases to the same struct) instead of silently first-picking, citing #199b/#10. Symmetric across cstage (cmd/w6c/cgen.c) and wwstage (selfhost/cmd/wcc/cgenutil.ww). One-level NAMED unwrap (chained ptr-aliases unmatched, unexercised -> #17). Adds test/wcc/789 (positive widen byte-id+runtime + degenerate-ambiguity reject guard, both stages). Unblocks post-eFinal #5's handle surface. rule-10 fix-up.
This commit is contained in:
2026-05-30 09:04:33 +09:00
parent 419c896ad0
commit 45e5d74047
6 changed files with 453 additions and 0 deletions

View File

@@ -661,10 +661,46 @@ cg_tag_for_variant(Type *t, Type *vt)
if (t == NULL || vt == NULL) return -1;
if (t->kind == TY_NAMED) t = t->under;
if (t == NULL || t->kind != TY_TAGGED) return -1;
/* Pass 1: exact match (NAMED-vs-NAMED pointer-id, tagged-vs-tagged,
* bare type_eq). Exact matches take precedence and need no guard —
* distinct variants don't exact-match the same source. */
int idx = 0;
for (Tparam *p = t->params; p; p = p->next, idx++) {
if (cg_variant_match(p->type, vt)) return idx;
}
/* Pass 2 (#15): no exact variant matched — try a structural match of
* a BARE source against a NAMED-alias variant (e.g. a bare `*vtable`
* into the `stream` (= *vtable) variant of `(file | stream)`). The
* bare side has no nominal identity, so structure is the only
* discriminator; without this the widen found no variant and
* defaulted to tag 0, miscompiling every io.write(&...vt) in cgen's
* emit path. Exact-first (pass 1) keeps a bare `i64` into
* `(i64 | oserror)` binding the exact `i64`, not the alias. drew's
* proviso: guard the structural fallback like the #218 nested-widen
* site — if a bare source structurally matches >=2 NAMED variants,
* nominal layout is needed to disambiguate, so hard-error rather
* than silently first-pick. */
if (vt->kind != TY_NAMED) {
int found = -1, n = 0;
idx = 0;
for (Tparam *p = t->params; p; p = p->next, idx++) {
/* One-level NAMED unwrap: a chained ptr-alias variant
* (type a=*X; type b=a) isn't reached here, so it would
* silently mis-tag — unexercised (zero in corpus), see
* task #17. */
Type *pu = p->type;
if (pu && pu->kind == TY_NAMED && pu->under
&& type_eq(pu->under, vt)) {
if (found < 0) found = idx;
n++;
}
}
if (n >= 2)
fatal("cg_tag_for_variant: bare source structurally "
"matches >=2 NAMED variants — ambiguous without "
"nominal layout (#15/#218/#199b/#10)");
return found;
}
return -1;
}