Files
ww/selfhost
Hojun-Cho 176904dffc w6c+wwstage: tag the outer widen of a nested multi-variant union (#218)
The outer widen of a NAMED multi-variant union value into an enclosing union
mis-tagged: the store took the tagged-subset path (inner value at slot+0 plus
a sub-variant remap, collapsing every inner sub-variant onto outer tag 0),
while the match-extract reads the nested layout (outer tag at +0, inner 16B
value at +8). Store and extract disagreed, so the match selected the first
arm. Pre-existing silent miscompile, latent because error-origination sites
(`let e: io.error = <leaf>; return e`) were gate-blind — no test discriminated
a freshly-originated error at a branched caller; the io vstream surface is the
first to do so.

Fix, both stages, byte-identical: cg_variant_match (cmd/w6c/cgen.c) and its
wwstage mirror cgvariantmatch (cgenutil.ww) fall back to structural equality
of the unwrapped tagged unions when the alias collapse loses nominal identity
(a NAMED outer variant vs an unwrapped-tagged source); the widen store now
writes the inner value at slot+8 and the outer tag at +0, matching the
extract. The inner union's build/payload/extract already worked (a destructure
through the outer round-trip recovers the inner payload) — only the
outer-widen store was wrong.

Collision guard (the fallback is unsound without it): structural matching
cannot disambiguate two nominally-distinct same-shape variants in one outer
union. That is unreachable under today's nominal-lossy collapse but inverts
the moment #199b lands the nominal layer, so if >=2 outer variants
structurally match the source we hard-error at compile time citing #199b —
both stages, an enforced invariant rather than a "rare, trust it" assumption.

Folds #219: the wwstage tinfo typeeq (lib/ww/typ.ww) had no TY_TAGGED branch
and fell through to `return true` (any two tagged unions compared equal);
cstage type_eq (type.c:269) has the structural branch. The structural fallback
above is the first and only caller to compare two bare tagged unions, so #219
is unexercised — and therefore ungateable — in isolation; it folds here per
the rule-11 couldn't-split carve-out (same structural reason as #206's
N_TTUPLE fold). The added branch mirrors cstage type_eq, tightening wwstage
into alignment.

test/wcc/925_nested_union_widen_run: outer-arm select, destructure-after-
propagation (payload survives the round-trip), destructure-let, single-variant
control, and the collision-guard compile-error, each with a cstage==wwstage
byte-id check (the path is gate-blind). Interim until #199b/B-full lands the
true nominal wrapped-slot layout.
2026-05-29 23:19:36 +09:00
..