cgen: #33 wwstage untyped-source variant match — mirror cg_variant_match's type_assignable arm

A bare untyped init into a tagged union resolved its variant through
taggedvariantindext's str/slice SHAPE fallback, whose first
non-str/slice variant can be void: `let e: (void | size) = 5` stored
tag 0 while the is/as side resolved size to 1 — runtime-FALSE is-test
on wwstage only (cstage resolves untyped sources in cg_variant_match
:801 via type_assignable; cs=0/ww=1 on ken's f33, divergent asm,
gate-blind).

Fix adds the untyped-source arm at the top of cgvariantmatch — the
single flatvariantidxt pass-1 predicate, same funnel position as
cstage — backed by tyassignableuntyped, a focused tinfo-keyed mirror
of type_assignable's untyped→typed subset (cmd/wcc/type.c:355-370)
plus its concrete→tagged variant drill (:316-324). Typed/loose
sources keep the shape fallback unchanged; tuple-in-union keeps its
AST-shape loud-stops (#241/#242, checked: TY_TUPLE is never untyped).

Probes converge byte-id: f33 let-init, assign-after-void, bool-leading
skip, bare arg widen, untyped-str, cast no-drift control. 938 row
untyped_int_bare_widen pins all of them (pre-fix ww_run=1 at 322667b).

Frees the fold-5b at-site #33 workaround (lib/regex/regex.ww:772
cast-form pin `min = (0: size)`) — the bare Hare-verbatim `min = 0`
spelling now tags correctly; the regex comment update rides the next
regex touch.

Task #33.
This commit is contained in:
2026-06-05 08:07:08 +09:00
parent 322667b820
commit 9fad59354f
4 changed files with 278 additions and 43 deletions

View File

@@ -55,13 +55,12 @@
*
* Field readback goes through MATCH BINDINGS only: direct field reads
* on a struct-w/-tagged-field LOCAL are the open #24 (wwstage
* field(SB) fallback), and tagged-field inits use explicit `: size`
* casts: the BARE untyped-int form is the open #33 wwstage mis-tag.
* Both are filed separately and out of #23's arm. A tagged field
* inside a NESTED plain-struct field is the open #38 family (fails
* identically for plain locals — pre-existing in the canonical fill/
* read path, NOT a #23 regression; probed both ways at 6699158) and
* is deliberately NOT a row here.
* field(SB) fallback). Tagged-field inits use explicit `: size`
* casts in the #23 rows; the BARE untyped-int form (the old #33
* wwstage mis-tag) is pinned by its own row (untyped_int_bare_widen)
* since the cgvariantmatch untyped arm landed. The chained-dot
* tagged-leaf read/assign family (#38a) is pinned by the
* chained_* rows below since the dot-spine tagged arms landed.
*
* Every K_RUN row also asserts cstage/wwstage asm byte-id. NNN<950,
* self-contained (/tmp, no imports) — rule-14's selfhost-sibling race
@@ -427,6 +426,38 @@ static const struct row rows[] = {
" };\n"
" return 0;\n"
"};\n", 0, 0 },
/* #33: a BARE untyped-int init into (void|T) — pre-fix wwstage's
* variant scan fell to the str/slice shape fallback whose first
* non-str/slice variant is `void` (tag 0 stored, is-test expects
* 1; cstage resolved via type_assignable). Pins let-init, assign
* after void, arg widen, the bool-leading skip, and the signed
* (void|i64) variant (ken hB3); the cast form rides alongside as
* the no-drift control (it was always right). */
{ "untyped_int_bare_widen",
"package main;\n"
"fn take(v: (void | size)) i32 = {\n"
" if (v is size) { return (v as size): i32; };\n"
" return -1;\n"
"};\n"
"export fn main() i32 = {\n"
" let e: (void | size) = 5;\n"
" if (!(e is size)) { return 1; };\n"
" if (e as size != 5) { return 2; };\n"
" let mn: (void | size) = void;\n"
" mn = 7;\n"
" if (!(mn is size)) { return 3; };\n"
" if (mn as size != 7) { return 4; };\n"
" let b: (bool | u64) = 9;\n"
" if (!(b is u64)) { return 5; };\n"
" if (take(9) != 9) { return 6; };\n"
" let cf: (void | size) = 5: size;\n"
" if (!(cf is size)) { return 7; };\n"
" if (cf as size != 5) { return 8; };\n"
" let f: (void | i64) = 9;\n"
" if (!(f is i64)) { return 9; };\n"
" if (f as i64 != 9) { return 10; };\n"
" return 0;\n"
"};\n", 0, 0 },
};
static int