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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user