wcc/ww: flexible rune-const promotion at scalar seams (coercerunelit)

wwstage stamped N_RUNELIT as concrete rune (cstage: untyped_rune,
check.c:1296), so an uncast rune literal into a fitting integer slot
over-rejected where harec accepts (promote_flexible). Add coercerunelit
(mirror of coercefloatlit) at the scalar seams — let-init, return,
call-arg, index-store; coarse accept, no range check (mirror cstage
type.c:379); array-literal elements keep the range gate
(arrlit_init_fits, types.c:923). Teeth: the fnmatch.ww '\\': u8
workaround cast and getopttest's 'X': u8 index-store casts are removed
and compile green. Checker-only (no e.type_ restamp): byte-id held.
This commit is contained in:
2026-06-11 20:41:20 +09:00
parent fa17459558
commit 8d2d157a58
5 changed files with 216 additions and 13 deletions

View File

@@ -12756,6 +12756,48 @@ fn coercefloatlit(c: *checker, e: *node, target: *node) void = {
};
};
// coercerunelit — #29: an un-suffixed rune literal narrowing into an
// integer slot. Same intent as coercefloatlit (:2324) but CHECKER-only:
// wwstage stamps N_RUNELIT concrete `rune` (:2652), so isassignable's
// untyped arms never fire and the operand falls to the "two known
// primitives, different names → confident reject" arm (:4139) — the #29
// over-reject. cstage stamps N_RUNELIT untyped_rune (cmd/wcc/check.c:1296)
// which type_assignable admits into ANY integer UNCONDITIONALLY
// (cmd/wcc/type.c:379 — coarse, NO range check; harec's range-precise
// promote_flexible at types.c:928 is the reference cstage already
// flattened, so wwstage mirrors cstage's coarse rule, not harec's gate).
// Return the integer target tnode so the caller overrides its `src`/
// `atype` and isassignable sees su==target → accept. Does NOT touch
// e.type_: cgen narrows a rune immediate by the DESTINATION width (proven
// byte-id at let/call-arg/index-store; #251 array-elem twin) and for a
// tagged-union target the variant boxing falls to taggedvariantindext's
// scalar-shape fallback (cgenutil.ww:3054) which picks the first scalar
// variant — the same u8 variant cstage's untyped_rune boxes into — so the
// unchanged node stamp keeps cgen provably identical to the pre-fix
// bare-literal lowering. Array-LITERAL elements are NOT routed here: they
// keep harec's per-element range gate via checkarrlitfits's foldint /
// defcastfits path (:4634). For a tagged-union target (fnmatch pat_next's
// (u8 | star | ...)) return the first integer variant.
fn coercerunelit(c: *checker, e: *node, target: *node) *node = {
if (e == nil) { return nil; };
if (e.kind != nkind.N_RUNELIT) { return nil; };
if (target == nil) { return nil; };
let tu: *node = resolvealias(c, unwrapbang(target));
if (tu == nil) { return nil; };
if (isinttypeast(tu)) { return tu; };
if (tu.kind == nkind.N_TTAGGED) {
let v: *node = tu.list;
for (v != nil) {
let vu: *node = resolvealias(c, unwrapbang(v));
if (vu != nil) {
if (isinttypeast(vu)) { return vu; };
};
v = v.next;
};
};
return nil;
};
// binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons /
@@ -15215,6 +15257,14 @@ fn desugarcallargs(c: *checker, n: *node) void = {
if (param.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil);
// #29: a rune literal narrowing into an integer param
// (`take('b')` where take(b: u8)). Override atype to the
// integer target so c3's general isassignable accepts,
// mirroring cstage's coarse untyped_rune rule. See
// coercerunelit. cgen is byte-id-neutral (narrows by the
// param/destination width).
let runet: *node = coercerunelit(c, a, param.lhs);
if (runet != nil) { atype = runet; };
// #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure.
@@ -15267,6 +15317,13 @@ fn checkassign(c: *checker, n: *node) void = {
if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, nil);
// #29: a rune literal narrowing into an integer assign/index-store
// target (`buf[i] = 'F'`). Override rtn to the integer target so a
// general assign typecheck accepts, mirroring cstage's coarse
// untyped_rune rule. See coercerunelit. cgen narrows by the destination
// width (byte-id-neutral). Removes the getopt `'X': u8` index casts.
let runet: *node = coercerunelit(c, n.rhs, ltn);
if (runet != nil) { rtn = runet; };
// #31/#33: bare array-literal rhs has no backing — loud-reject
// (supported only at a `let`).
if (!rejectarrlitborrow(c, ltn, n.rhs)) {
@@ -15508,6 +15565,11 @@ fn checkletassign(c: *checker, n: *node) void = {
n.rhs.lhs = arr;
src = arr;
};
// #29: an un-suffixed rune literal narrowing into an integer let target
// (`let b: u8 = 'a'`). Override src to the integer target so isassignable
// accepts, mirroring cstage's coarse untyped_rune rule. See coercerunelit.
let runet: *node = coercerunelit(c, n.rhs, n.lhs);
if (runet != nil) { src = runet; };
let conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf);
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
@@ -15573,6 +15635,12 @@ fn checkretassign(c: *checker, n: *node) void = {
};
let src: *node = exprtype(c, n.lhs, nil);
if (src == nil) { return; };
// #29: a rune literal returned into an integer (or integer-variant)
// fnret (`return '\\';` into u8 or (u8 | star | ...)). Override src so
// isassignable accepts; cgen boxes via the shape fallback. See
// coercerunelit. Removes the fnmatch.ww:126 `'\\': u8` workaround cast.
let runet: *node = coercerunelit(c, n.lhs, c.fnret);
if (runet != nil) { src = runet; };
let conf: bool = false;
let ok: bool = isassignable(c, c.fnret, src, &conf);
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.