wcc/ww: binoptype/unoptype operand-kind gates (arith/bitwise/ordered/logical)

wwstage ran no operand-kind check on binary/unary operators: str+str
compiled to integer ADD on the 24B header (silent garbage). Gate each
operator class on the operand kind, mirroring cstage check.c:1186-1238
wording; the pre-existing ptr-arith arm aligns to intkindast so
ptr+untyped_int keeps compiling (byte-id-neutral, the over-reject the
self-compile gate caught). New intkindast/numkindast/boolkindast
predicates.
This commit is contained in:
2026-06-12 11:14:48 +09:00
parent 6f11763462
commit 353b50489f
3 changed files with 351 additions and 24 deletions

View File

@@ -12879,13 +12879,19 @@ fn binoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let ltn: *node = exprtype(c, e.lhs, nil);
let rtn: *node = exprtype(c, e.rhs, nil);
// #38/F2: ptr ± int / int + ptr use intkindast (the type_isint mirror,
// incl untyped_int / untyped_rune / alias-chased) — NOT isinttypeast,
// which misses untyped_int. cstage's ptr-arith arm gates on type_isint
// (check.c:1179/1181), so `p + 1` (untyped_int) returns the ptr there
// and never reaches the isnum gate; aligning these arms keeps the new
// arithmetic gate below from rejecting valid pointer arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && intkindast(c, rtn)) {
return ltn;
};
};
if (op == tkind.TK_PLUS) {
if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
if (intkindast(c, ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
return rtn;
};
};
@@ -12895,11 +12901,28 @@ fn binoptype(c: *checker, e: *node) *node = {
return mktname(c, "i64");
};
};
// #38/F2 (review item 5): operand-kind gates the wwstage checker
// elided. Mirror cstage cbinop (cmd/wcc/check.c:1186-1199): arithmetic
// wants numeric operands, bitwise wants integer operands. Without these
// `let c: str = a + b;` (str+str) compiled to an integer ADD of the two
// header pointers, silent garbage. The ptr-arithmetic special cases
// above already returned, so a survivor here is plain value arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS ||
op == tkind.TK_STAR || op == tkind.TK_SLASH ||
op == tkind.TK_PERCENT || op == tkind.TK_AMP ||
op == tkind.TK_PIPE || op == tkind.TK_CARET ||
op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) {
op == tkind.TK_PERCENT) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "arithmetic on non-numeric type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_AMP || op == tkind.TK_PIPE ||
op == tkind.TK_CARET || op == tkind.TK_LSHIFT ||
op == tkind.TK_RSHIFT) {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
deffolderr(c, e, "bitwise on non-integer type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_EQ || op == tkind.TK_NEQ ||
@@ -12918,9 +12941,30 @@ fn binoptype(c: *checker, e: *node) *node = {
deffolderr(c, e, "operands have differing types");
};
};
// #38/F2 (review item 5): ordered comparisons want numeric
// operands (cstage check.c:1198-1199). EQ/NEQ stay UNGATED —
// cstage's cbinop equality arm (check.c:1194-1196) gates nothing,
// so str==str / ptr==ptr remain valid; aligning those down would
// over-reject what cstage accepts.
if (op == tkind.TK_LT || op == tkind.TK_LE ||
op == tkind.TK_GT || op == tkind.TK_GE) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "ordered comparison on non-numeric");
};
};
return mktname(c, "bool");
};
if (op == tkind.TK_AND || op == tkind.TK_OR) {
// #38/F2 (review item 5): logical operands must be bool (cstage
// check.c:1208-1211 gates each side via type_chase_named). cstage
// formats per-side with tokname; ww's piecewise cerr can't splice
// the op token, so one combined wording — the build-based reject
// test gates on rc, not on exact text.
if ((ltn != nil && !boolkindast(c, ltn)) ||
(rtn != nil && !boolkindast(c, rtn))) {
deffolderr(c, e, "logical operand is not bool");
};
return mktname(c, "bool");
};
// Unreachable for valid input: op is one of TK_PLUS/MINUS/STAR/
@@ -12938,9 +12982,32 @@ fn binoptype(c: *checker, e: *node) *node = {
fn unoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let opt: *node = exprtype(c, e.lhs, nil);
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; };
if (op == tkind.TK_NOT) { return mktname(c, "bool"); };
if (op == tkind.TK_TILDE) { return opt; };
// #38/F2 (review item 5): unop operand-kind gates the wwstage checker
// elided. Mirror cstage cunop (cmd/wcc/check.c:1224-1238): unary +/-
// want a numeric operand, ~ wants an integer, ! wants a bool. A nil
// opt is an already-errored / inherent-IDENT bail — not re-rejected.
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) {
if (opt != nil && !numkindast(c, opt)) {
if (op == tkind.TK_MINUS) {
deffolderr(c, e, "- on non-numeric");
} else {
deffolderr(c, e, "+ on non-numeric");
};
};
return opt;
};
if (op == tkind.TK_NOT) {
if (opt != nil && !boolkindast(c, opt)) {
deffolderr(c, e, "! on non-bool");
};
return mktname(c, "bool");
};
if (op == tkind.TK_TILDE) {
if (opt != nil && !intkindast(c, opt)) {
deffolderr(c, e, "~ on non-integer");
};
return opt;
};
if (op == tkind.TK_STAR) {
// opt nil here means the operand was an inherent-IDENT bail
// (exprtype N_IDENT arm L1596-1599 — SK_USE / pseudo-builtin
@@ -14328,6 +14395,48 @@ fn isinttypeast(t: *node) bool = {
return false;
};
// intkindast / numkindast / boolkindast — operand-kind classifiers for
// binoptype/unoptype's cstage-mirrored gates (#38/F2 review item 5).
// They resolvealias + unwrapbang to chase the alias (TY_NAMED) wrapper
// before classifying — exactly as cstage's type_isint/type_isnum recurse
// through TY_NAMED.under (cmd/wcc/type.c:178-180/192-193) and its AND/OR
// arm chases via type_chase_named (check.c:1206-1207). Without the chase,
// a `type myint = i32` operand would spuriously fail the gate that cstage
// (chasing) accepts. nil operands are treated as already-errored upstream
// (the unifyarith nil-bail shapes) and NOT re-rejected — the cstage twin's
// `l == ty_err` escape.
fn intkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (isinttypeast(u)) { return true; }; // int family + enum + rune
if (isuntypedint(u)) { return true; };
if (u != nil && u.kind == nkind.N_TNAME && streq(u.str, "untyped_rune")) {
return true;
};
return false;
};
fn numkindast(c: *checker, t: *node) bool = {
if (intkindast(c, t)) { return true; };
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
let s: str = u.str;
if (streq(s, "f32")) { return true; };
if (streq(s, "f64")) { return true; };
if (streq(s, "untyped_float")) { return true; };
return false;
};
fn boolkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
return streq(u.str, "bool") || streq(u.str, "untyped_bool");
};
fn isnumerictname(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };

View File

@@ -2459,13 +2459,19 @@ fn binoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let ltn: *node = exprtype(c, e.lhs, nil);
let rtn: *node = exprtype(c, e.rhs, nil);
// #38/F2: ptr ± int / int + ptr use intkindast (the type_isint mirror,
// incl untyped_int / untyped_rune / alias-chased) — NOT isinttypeast,
// which misses untyped_int. cstage's ptr-arith arm gates on type_isint
// (check.c:1179/1181), so `p + 1` (untyped_int) returns the ptr there
// and never reaches the isnum gate; aligning these arms keeps the new
// arithmetic gate below from rejecting valid pointer arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && intkindast(c, rtn)) {
return ltn;
};
};
if (op == tkind.TK_PLUS) {
if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
if (intkindast(c, ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
return rtn;
};
};
@@ -2475,11 +2481,28 @@ fn binoptype(c: *checker, e: *node) *node = {
return mktname(c, "i64");
};
};
// #38/F2 (review item 5): operand-kind gates the wwstage checker
// elided. Mirror cstage cbinop (cmd/wcc/check.c:1186-1199): arithmetic
// wants numeric operands, bitwise wants integer operands. Without these
// `let c: str = a + b;` (str+str) compiled to an integer ADD of the two
// header pointers, silent garbage. The ptr-arithmetic special cases
// above already returned, so a survivor here is plain value arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS ||
op == tkind.TK_STAR || op == tkind.TK_SLASH ||
op == tkind.TK_PERCENT || op == tkind.TK_AMP ||
op == tkind.TK_PIPE || op == tkind.TK_CARET ||
op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) {
op == tkind.TK_PERCENT) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "arithmetic on non-numeric type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_AMP || op == tkind.TK_PIPE ||
op == tkind.TK_CARET || op == tkind.TK_LSHIFT ||
op == tkind.TK_RSHIFT) {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
deffolderr(c, e, "bitwise on non-integer type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_EQ || op == tkind.TK_NEQ ||
@@ -2498,9 +2521,30 @@ fn binoptype(c: *checker, e: *node) *node = {
deffolderr(c, e, "operands have differing types");
};
};
// #38/F2 (review item 5): ordered comparisons want numeric
// operands (cstage check.c:1198-1199). EQ/NEQ stay UNGATED —
// cstage's cbinop equality arm (check.c:1194-1196) gates nothing,
// so str==str / ptr==ptr remain valid; aligning those down would
// over-reject what cstage accepts.
if (op == tkind.TK_LT || op == tkind.TK_LE ||
op == tkind.TK_GT || op == tkind.TK_GE) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "ordered comparison on non-numeric");
};
};
return mktname(c, "bool");
};
if (op == tkind.TK_AND || op == tkind.TK_OR) {
// #38/F2 (review item 5): logical operands must be bool (cstage
// check.c:1208-1211 gates each side via type_chase_named). cstage
// formats per-side with tokname; ww's piecewise cerr can't splice
// the op token, so one combined wording — the build-based reject
// test gates on rc, not on exact text.
if ((ltn != nil && !boolkindast(c, ltn)) ||
(rtn != nil && !boolkindast(c, rtn))) {
deffolderr(c, e, "logical operand is not bool");
};
return mktname(c, "bool");
};
// Unreachable for valid input: op is one of TK_PLUS/MINUS/STAR/
@@ -2518,9 +2562,32 @@ fn binoptype(c: *checker, e: *node) *node = {
fn unoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let opt: *node = exprtype(c, e.lhs, nil);
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; };
if (op == tkind.TK_NOT) { return mktname(c, "bool"); };
if (op == tkind.TK_TILDE) { return opt; };
// #38/F2 (review item 5): unop operand-kind gates the wwstage checker
// elided. Mirror cstage cunop (cmd/wcc/check.c:1224-1238): unary +/-
// want a numeric operand, ~ wants an integer, ! wants a bool. A nil
// opt is an already-errored / inherent-IDENT bail — not re-rejected.
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) {
if (opt != nil && !numkindast(c, opt)) {
if (op == tkind.TK_MINUS) {
deffolderr(c, e, "- on non-numeric");
} else {
deffolderr(c, e, "+ on non-numeric");
};
};
return opt;
};
if (op == tkind.TK_NOT) {
if (opt != nil && !boolkindast(c, opt)) {
deffolderr(c, e, "! on non-bool");
};
return mktname(c, "bool");
};
if (op == tkind.TK_TILDE) {
if (opt != nil && !intkindast(c, opt)) {
deffolderr(c, e, "~ on non-integer");
};
return opt;
};
if (op == tkind.TK_STAR) {
// opt nil here means the operand was an inherent-IDENT bail
// (exprtype N_IDENT arm L1596-1599 — SK_USE / pseudo-builtin
@@ -3908,6 +3975,48 @@ fn isinttypeast(t: *node) bool = {
return false;
};
// intkindast / numkindast / boolkindast — operand-kind classifiers for
// binoptype/unoptype's cstage-mirrored gates (#38/F2 review item 5).
// They resolvealias + unwrapbang to chase the alias (TY_NAMED) wrapper
// before classifying — exactly as cstage's type_isint/type_isnum recurse
// through TY_NAMED.under (cmd/wcc/type.c:178-180/192-193) and its AND/OR
// arm chases via type_chase_named (check.c:1206-1207). Without the chase,
// a `type myint = i32` operand would spuriously fail the gate that cstage
// (chasing) accepts. nil operands are treated as already-errored upstream
// (the unifyarith nil-bail shapes) and NOT re-rejected — the cstage twin's
// `l == ty_err` escape.
fn intkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (isinttypeast(u)) { return true; }; // int family + enum + rune
if (isuntypedint(u)) { return true; };
if (u != nil && u.kind == nkind.N_TNAME && streq(u.str, "untyped_rune")) {
return true;
};
return false;
};
fn numkindast(c: *checker, t: *node) bool = {
if (intkindast(c, t)) { return true; };
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
let s: str = u.str;
if (streq(s, "f32")) { return true; };
if (streq(s, "f64")) { return true; };
if (streq(s, "untyped_float")) { return true; };
return false;
};
fn boolkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
return streq(u.str, "bool") || streq(u.str, "untyped_bool");
};
fn isnumerictname(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };

View File

@@ -12879,13 +12879,19 @@ fn binoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let ltn: *node = exprtype(c, e.lhs, nil);
let rtn: *node = exprtype(c, e.rhs, nil);
// #38/F2: ptr ± int / int + ptr use intkindast (the type_isint mirror,
// incl untyped_int / untyped_rune / alias-chased) — NOT isinttypeast,
// which misses untyped_int. cstage's ptr-arith arm gates on type_isint
// (check.c:1179/1181), so `p + 1` (untyped_int) returns the ptr there
// and never reaches the isnum gate; aligning these arms keeps the new
// arithmetic gate below from rejecting valid pointer arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && isinttypeast(rtn)) {
if (ltn != nil && ltn.kind == nkind.N_TPTR && intkindast(c, rtn)) {
return ltn;
};
};
if (op == tkind.TK_PLUS) {
if (isinttypeast(ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
if (intkindast(c, ltn) && rtn != nil && rtn.kind == nkind.N_TPTR) {
return rtn;
};
};
@@ -12895,11 +12901,28 @@ fn binoptype(c: *checker, e: *node) *node = {
return mktname(c, "i64");
};
};
// #38/F2 (review item 5): operand-kind gates the wwstage checker
// elided. Mirror cstage cbinop (cmd/wcc/check.c:1186-1199): arithmetic
// wants numeric operands, bitwise wants integer operands. Without these
// `let c: str = a + b;` (str+str) compiled to an integer ADD of the two
// header pointers, silent garbage. The ptr-arithmetic special cases
// above already returned, so a survivor here is plain value arithmetic.
if (op == tkind.TK_PLUS || op == tkind.TK_MINUS ||
op == tkind.TK_STAR || op == tkind.TK_SLASH ||
op == tkind.TK_PERCENT || op == tkind.TK_AMP ||
op == tkind.TK_PIPE || op == tkind.TK_CARET ||
op == tkind.TK_LSHIFT || op == tkind.TK_RSHIFT) {
op == tkind.TK_PERCENT) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "arithmetic on non-numeric type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_AMP || op == tkind.TK_PIPE ||
op == tkind.TK_CARET || op == tkind.TK_LSHIFT ||
op == tkind.TK_RSHIFT) {
if ((ltn != nil && !intkindast(c, ltn)) ||
(rtn != nil && !intkindast(c, rtn))) {
deffolderr(c, e, "bitwise on non-integer type");
};
return unifyarith(c, ltn, rtn);
};
if (op == tkind.TK_EQ || op == tkind.TK_NEQ ||
@@ -12918,9 +12941,30 @@ fn binoptype(c: *checker, e: *node) *node = {
deffolderr(c, e, "operands have differing types");
};
};
// #38/F2 (review item 5): ordered comparisons want numeric
// operands (cstage check.c:1198-1199). EQ/NEQ stay UNGATED —
// cstage's cbinop equality arm (check.c:1194-1196) gates nothing,
// so str==str / ptr==ptr remain valid; aligning those down would
// over-reject what cstage accepts.
if (op == tkind.TK_LT || op == tkind.TK_LE ||
op == tkind.TK_GT || op == tkind.TK_GE) {
if ((ltn != nil && !numkindast(c, ltn)) ||
(rtn != nil && !numkindast(c, rtn))) {
deffolderr(c, e, "ordered comparison on non-numeric");
};
};
return mktname(c, "bool");
};
if (op == tkind.TK_AND || op == tkind.TK_OR) {
// #38/F2 (review item 5): logical operands must be bool (cstage
// check.c:1208-1211 gates each side via type_chase_named). cstage
// formats per-side with tokname; ww's piecewise cerr can't splice
// the op token, so one combined wording — the build-based reject
// test gates on rc, not on exact text.
if ((ltn != nil && !boolkindast(c, ltn)) ||
(rtn != nil && !boolkindast(c, rtn))) {
deffolderr(c, e, "logical operand is not bool");
};
return mktname(c, "bool");
};
// Unreachable for valid input: op is one of TK_PLUS/MINUS/STAR/
@@ -12938,9 +12982,32 @@ fn binoptype(c: *checker, e: *node) *node = {
fn unoptype(c: *checker, e: *node) *node = {
let op: tkind = e.op;
let opt: *node = exprtype(c, e.lhs, nil);
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) { return opt; };
if (op == tkind.TK_NOT) { return mktname(c, "bool"); };
if (op == tkind.TK_TILDE) { return opt; };
// #38/F2 (review item 5): unop operand-kind gates the wwstage checker
// elided. Mirror cstage cunop (cmd/wcc/check.c:1224-1238): unary +/-
// want a numeric operand, ~ wants an integer, ! wants a bool. A nil
// opt is an already-errored / inherent-IDENT bail — not re-rejected.
if (op == tkind.TK_MINUS || op == tkind.TK_PLUS) {
if (opt != nil && !numkindast(c, opt)) {
if (op == tkind.TK_MINUS) {
deffolderr(c, e, "- on non-numeric");
} else {
deffolderr(c, e, "+ on non-numeric");
};
};
return opt;
};
if (op == tkind.TK_NOT) {
if (opt != nil && !boolkindast(c, opt)) {
deffolderr(c, e, "! on non-bool");
};
return mktname(c, "bool");
};
if (op == tkind.TK_TILDE) {
if (opt != nil && !intkindast(c, opt)) {
deffolderr(c, e, "~ on non-integer");
};
return opt;
};
if (op == tkind.TK_STAR) {
// opt nil here means the operand was an inherent-IDENT bail
// (exprtype N_IDENT arm L1596-1599 — SK_USE / pseudo-builtin
@@ -14328,6 +14395,48 @@ fn isinttypeast(t: *node) bool = {
return false;
};
// intkindast / numkindast / boolkindast — operand-kind classifiers for
// binoptype/unoptype's cstage-mirrored gates (#38/F2 review item 5).
// They resolvealias + unwrapbang to chase the alias (TY_NAMED) wrapper
// before classifying — exactly as cstage's type_isint/type_isnum recurse
// through TY_NAMED.under (cmd/wcc/type.c:178-180/192-193) and its AND/OR
// arm chases via type_chase_named (check.c:1206-1207). Without the chase,
// a `type myint = i32` operand would spuriously fail the gate that cstage
// (chasing) accepts. nil operands are treated as already-errored upstream
// (the unifyarith nil-bail shapes) and NOT re-rejected — the cstage twin's
// `l == ty_err` escape.
fn intkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (isinttypeast(u)) { return true; }; // int family + enum + rune
if (isuntypedint(u)) { return true; };
if (u != nil && u.kind == nkind.N_TNAME && streq(u.str, "untyped_rune")) {
return true;
};
return false;
};
fn numkindast(c: *checker, t: *node) bool = {
if (intkindast(c, t)) { return true; };
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
let s: str = u.str;
if (streq(s, "f32")) { return true; };
if (streq(s, "f64")) { return true; };
if (streq(s, "untyped_float")) { return true; };
return false;
};
fn boolkindast(c: *checker, t: *node) bool = {
if (t == nil) { return false; };
let u: *node = resolvealias(c, unwrapbang(t));
if (u == nil) { return false; };
if (u.kind != nkind.N_TNAME) { return false; };
return streq(u.str, "bool") || streq(u.str, "untyped_bool");
};
fn isnumerictname(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };