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

@@ -123,8 +123,7 @@ fn pat_next(pat: str, pos: *i32, fl: flag) (u8 | star | question | bracket | end
*pos += 1; *pos += 1;
return c2; return c2;
}; };
// #29: wwstage over-rejects the implicit flexible-rune-const narrow (harec promote_flexible accepts); explicit cast until flexible-const promotion lands — revert to bare '\\' then. return '\\';
return '\\': u8;
}; };
return c; return c;
}; };

View File

@@ -63,10 +63,10 @@ fn streq(a: str, b: str) bool = {
// (wantflag, wantval) parallel arrays, indexed by option order. // (wantflag, wantval) parallel arrays, indexed by option order.
let wantf: [4]u8; let wantf: [4]u8;
let wantv: [4]str; let wantv: [4]str;
wantf[0] = 'F': u8; wantv[0] = ""; wantf[0] = 'F'; wantv[0] = "";
wantf[1] = 'a': u8; wantv[1] = ""; wantf[1] = 'a'; wantv[1] = "";
wantf[2] = 'h': u8; wantv[2] = ""; wantf[2] = 'h'; wantv[2] = "";
wantf[3] = 's': u8; wantv[3] = ""; wantf[3] = 's'; wantv[3] = "";
let i: i32 = 0; let i: i32 = 0;
for (i < 4) { for (i < 4) {
@@ -108,8 +108,8 @@ fn streq(a: str, b: str) bool = {
let wantf: [2]u8; let wantf: [2]u8;
let wantv: [2]str; let wantv: [2]str;
wantf[0] = 'e': u8; wantv[0] = "s/foo/bar/"; wantf[0] = 'e'; wantv[0] = "s/foo/bar/";
wantf[1] = 'f': u8; wantv[1] = "/tmp/x.sed"; wantf[1] = 'f'; wantv[1] = "/tmp/x.sed";
let i: i32 = 0; let i: i32 = 0;
for (i < 2) { for (i < 2) {
@@ -280,9 +280,9 @@ fn streq(a: str, b: str) bool = {
let argn: [3]i32; let argn: [3]i32;
let wantk: [3]i32; let wantk: [3]i32;
let wantf: [3]u8; let wantf: [3]u8;
argo[0] = 0; argn[0] = 2; wantk[0] = 1; wantf[0] = 'x': u8; // UNKNOWNOPT=1 argo[0] = 0; argn[0] = 2; wantk[0] = 1; wantf[0] = 'x'; // UNKNOWNOPT=1
argo[1] = 2; argn[1] = 2; wantk[1] = 0; wantf[1] = 'e': u8; // REQUIRESARG=0 argo[1] = 2; argn[1] = 2; wantk[1] = 0; wantf[1] = 'e'; // REQUIRESARG=0
argo[2] = 4; argn[2] = 3; wantk[2] = 0; wantf[2] = 'e': u8; argo[2] = 4; argn[2] = 3; wantk[2] = 0; wantf[2] = 'e';
let i: i32 = 0; let i: i32 = 0;
for (i < 3) { for (i < 3) {
@@ -316,8 +316,8 @@ fn streq(a: str, b: str) bool = {
let flags: [2]u8; let flags: [2]u8;
let names: [2]str; let names: [2]str;
let wants: [2]str; let wants: [2]str;
kinds[0]=1; flags[0]='x': u8; names[0]="prog"; wants[0]="prog: unrecognized option: -x"; kinds[0]=1; flags[0]='x'; names[0]="prog"; wants[0]="prog: unrecognized option: -x";
kinds[1]=0; flags[1]='e': u8; names[1]="sed"; wants[1]="sed: option -e requires an argument"; kinds[1]=0; flags[1]='e'; names[1]="sed"; wants[1]="sed: option -e requires an argument";
let i: i32 = 0; let i: i32 = 0;
for (i < 2) { for (i < 2) {

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. // binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes // Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / // 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.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) { if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil); 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 // #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject, // MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure. // mirror cstage's call-arg type_assignable failure.
@@ -15267,6 +15317,13 @@ fn checkassign(c: *checker, n: *node) void = {
if (n.rhs == nil) { return; }; if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil); let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, 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 // #31/#33: bare array-literal rhs has no backing — loud-reject
// (supported only at a `let`). // (supported only at a `let`).
if (!rejectarrlitborrow(c, ltn, n.rhs)) { if (!rejectarrlitborrow(c, ltn, n.rhs)) {
@@ -15508,6 +15565,11 @@ fn checkletassign(c: *checker, n: *node) void = {
n.rhs.lhs = arr; n.rhs.lhs = arr;
src = 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 conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf); let ok: bool = isassignable(c, n.lhs, src, &conf);
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot. // #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); let src: *node = exprtype(c, n.lhs, nil);
if (src == nil) { return; }; 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 conf: bool = false;
let ok: bool = isassignable(c, c.fnret, src, &conf); let ok: bool = isassignable(c, c.fnret, src, &conf);
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`. // #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.

View File

@@ -2336,6 +2336,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. // binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes // Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / // returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons /
@@ -4795,6 +4837,14 @@ fn desugarcallargs(c: *checker, n: *node) void = {
if (param.kind == nkind.N_PARAM) { if (param.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) { if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil); 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 // #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject, // MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure. // mirror cstage's call-arg type_assignable failure.
@@ -4847,6 +4897,13 @@ fn checkassign(c: *checker, n: *node) void = {
if (n.rhs == nil) { return; }; if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil); let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, 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 // #31/#33: bare array-literal rhs has no backing — loud-reject
// (supported only at a `let`). // (supported only at a `let`).
if (!rejectarrlitborrow(c, ltn, n.rhs)) { if (!rejectarrlitborrow(c, ltn, n.rhs)) {
@@ -5088,6 +5145,11 @@ fn checkletassign(c: *checker, n: *node) void = {
n.rhs.lhs = arr; n.rhs.lhs = arr;
src = 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 conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf); let ok: bool = isassignable(c, n.lhs, src, &conf);
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot. // #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
@@ -5153,6 +5215,12 @@ fn checkretassign(c: *checker, n: *node) void = {
}; };
let src: *node = exprtype(c, n.lhs, nil); let src: *node = exprtype(c, n.lhs, nil);
if (src == nil) { return; }; 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 conf: bool = false;
let ok: bool = isassignable(c, c.fnret, src, &conf); let ok: bool = isassignable(c, c.fnret, src, &conf);
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`. // #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.

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. // binoptype — derive the result tnode of an N_BIN operator expression.
// Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes // Mirrors cstage cmd/wcc/check.c:598-640 `cbinop`. Operates on tnodes
// returned by exprtype; ptr arithmetic / bitwise / shifts / comparisons / // 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.kind == nkind.N_PARAM) {
if (param.op != tkind.TK_ELLIPSIS) { if (param.op != tkind.TK_ELLIPSIS) {
let atype: *node = exprtype(c, a, nil); 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 // #258: an array arg into a []T param with a
// MISMATCHED element is not a borrow — loud reject, // MISMATCHED element is not a borrow — loud reject,
// mirror cstage's call-arg type_assignable failure. // mirror cstage's call-arg type_assignable failure.
@@ -15267,6 +15317,13 @@ fn checkassign(c: *checker, n: *node) void = {
if (n.rhs == nil) { return; }; if (n.rhs == nil) { return; };
let ltn: *node = exprtype(c, n.lhs, nil); let ltn: *node = exprtype(c, n.lhs, nil);
let rtn: *node = exprtype(c, n.rhs, 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 // #31/#33: bare array-literal rhs has no backing — loud-reject
// (supported only at a `let`). // (supported only at a `let`).
if (!rejectarrlitborrow(c, ltn, n.rhs)) { if (!rejectarrlitborrow(c, ltn, n.rhs)) {
@@ -15508,6 +15565,11 @@ fn checkletassign(c: *checker, n: *node) void = {
n.rhs.lhs = arr; n.rhs.lhs = arr;
src = 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 conf: bool = false;
let ok: bool = isassignable(c, n.lhs, src, &conf); let ok: bool = isassignable(c, n.lhs, src, &conf);
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot. // #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); let src: *node = exprtype(c, n.lhs, nil);
if (src == nil) { return; }; 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 conf: bool = false;
let ok: bool = isassignable(c, c.fnret, src, &conf); let ok: bool = isassignable(c, c.fnret, src, &conf);
// #206: direct `&fn` returned into a `*alias` / `(*alias | void)`. // #206: direct `&fn` returned into a `*alias` / `(*alias | void)`.