selfhost/cmd/wcc: collapse type-kind predicates onto n.type_ (A.6.3b, #46)

The node-keyed kind helpers (typeis8byteprimitive, isstrtype/raw,
isslicetype/raw, istaggedtype/raw, isfloattype, isf32type/raw,
isf64typeraw, isnullabletype) each re-walked TNAME aliases via
aliaslookup and peeled TBANG by hand — duplicating cstage's single-
peel kind predicates at the AST level. After A.6.2 every type-AST
kind these read is tinfo-stamped at check.ww L426-436, and
tinfofornode collapses N_TBANG (check.ww:1145-1152) and the TY_NAMED
chain, so each predicate folds to one tinfo read.

Six new tinfo helpers in lib/ww/typ.ww mirror their cstage SSoT
verbatim:

  typeisstr      — cstage cgen.c:159 `type_isstr`     (TY_STR / TY_UNTYPED_STR)
  typeisslice    — cstage cgen.c:174 `type_isslice`
  typeistagged   — cstage cgen.c:516 `type_istagged`
  typeisf32      — cstage cgen.c:188 `type_isf32`
  typeisnullable — cstage cgen.c:396 `type_isnullable` (reads tinfo.nullable
                                                       stamped at check.ww:1309-1318)
  typeis8byteprim — cstage cgen.c N_LET sz==8 ladder (slot-pad set)

Rule 9 carve-out per the A.6.3a precedent: each helper has a named
cstage counterpart; the wwstage shape mirrors it directly. The five
dead AST-walking variants (isstrtyperaw, isslicetyperaw,
istaggedtyperaw, isf32typeraw, isf64typeraw) are deleted; the five
remaining callsites (cgenstmt cglet / cgmlet str-routing, cgenexpr
cgdot tuple-field) graduate to the alias-aware isstrtype(c, t).

nullableptrtag stays AST-keyed for now — tinfofornode doesn't
populate TY_TAGGED.params (check.ww:1287-1337 sets size / align /
nullable but not the variant chain), so the tinfo equivalent of
cstage cgen.c:405 `nullable_ptr_tag` can't read params today. WHY
comment at the site cites #50 / A.6.3f as the graduation point,
alongside the variant-index work and the tparam-population glue.

Byte-identity (994/995) is the behavior gate; full `make test` green
at 133/133 confirms.
This commit is contained in:
2026-05-22 05:53:34 +09:00
parent 03e4718199
commit b8e5a921f8
6 changed files with 405 additions and 540 deletions

View File

@@ -336,6 +336,94 @@ export fn typeissigned(t: *tinfo) bool = {
return typeisint(t);
};
// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default).
// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts
// the same untyped form. ww walks the .under chain so alias-of-alias
// (`type s2 = s1; type s1 = str;`) lands the same way.
export fn typeisstr(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_STR) { return true; };
if (k == tykind.TY_UNTYPED_STR) { return true; };
if (k == tykind.TY_NAMED) { return typeisstr(t.under); };
return false;
};
// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`.
export fn typeisslice(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_SLICE) { return true; };
if (k == tykind.TY_NAMED) { return typeisslice(t.under); };
return false;
};
// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516
// `type_istagged` — same single-peel shape. The node-keyed wwstage
// helper this replaces also unwrapped a leading N_TBANG so
// `type error = !(invalid | overflow);` registered as tagged. Post-
// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145-
// 1152 returns the inner tinfo unchanged) so we recover the cstage
// semantics with the bare kind check + NAMED chase.
export fn typeistagged(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return true; };
if (k == tykind.TY_NAMED) { return typeistagged(t.under); };
return false;
};
// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias
// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs
// MOVSD and the SS-variant arithmetic / cast opcodes.
export fn typeisf32(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_F32) { return true; };
if (k == tykind.TY_NAMED) { return typeisf32(t.under); };
return false;
};
// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold.
// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is
// stamped by tinfofornode (check.ww:1309-1318) when the two-variant
// shape matches.
export fn typeisnullable(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return t.nullable != 0; };
if (k == tykind.TY_NAMED) { return typeisnullable(t.under); };
return false;
};
// typeis8byteprim — does this type take exactly one 8-byte stack
// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to
// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate?
// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8`
// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage
// helper this replaces predates tinfo and AST-walked TBANG / TNAME
// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and
// TY_NAMED.under carries the chain, so the tinfo walk handles every
// shape the AST walker did.
export fn typeis8byteprim(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_PTR) { return true; };
if (k == tykind.TY_FN) { return true; };
if (k == tykind.TY_CHAN) { return true; };
if (k == tykind.TY_SLICE) { return false; };
if (k == tykind.TY_TUPLE) { return false; };
if (k == tykind.TY_TAGGED) { return false; };
if (k == tykind.TY_STR) { return false; };
if (k == tykind.TY_STRUCT) { return false; };
if (k == tykind.TY_ARRAY) { return t.size == 8u64; };
if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); };
// Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/
// int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init
// in cstage's `sz==8` branch.
return true;
};
export fn typeisuntyped(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;

View File

@@ -6666,6 +6666,94 @@ export fn typeissigned(t: *tinfo) bool = {
return typeisint(t);
};
// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default).
// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts
// the same untyped form. ww walks the .under chain so alias-of-alias
// (`type s2 = s1; type s1 = str;`) lands the same way.
export fn typeisstr(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_STR) { return true; };
if (k == tykind.TY_UNTYPED_STR) { return true; };
if (k == tykind.TY_NAMED) { return typeisstr(t.under); };
return false;
};
// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`.
export fn typeisslice(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_SLICE) { return true; };
if (k == tykind.TY_NAMED) { return typeisslice(t.under); };
return false;
};
// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516
// `type_istagged` — same single-peel shape. The node-keyed wwstage
// helper this replaces also unwrapped a leading N_TBANG so
// `type error = !(invalid | overflow);` registered as tagged. Post-
// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145-
// 1152 returns the inner tinfo unchanged) so we recover the cstage
// semantics with the bare kind check + NAMED chase.
export fn typeistagged(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return true; };
if (k == tykind.TY_NAMED) { return typeistagged(t.under); };
return false;
};
// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias
// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs
// MOVSD and the SS-variant arithmetic / cast opcodes.
export fn typeisf32(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_F32) { return true; };
if (k == tykind.TY_NAMED) { return typeisf32(t.under); };
return false;
};
// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold.
// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is
// stamped by tinfofornode (check.ww:1309-1318) when the two-variant
// shape matches.
export fn typeisnullable(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return t.nullable != 0; };
if (k == tykind.TY_NAMED) { return typeisnullable(t.under); };
return false;
};
// typeis8byteprim — does this type take exactly one 8-byte stack
// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to
// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate?
// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8`
// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage
// helper this replaces predates tinfo and AST-walked TBANG / TNAME
// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and
// TY_NAMED.under carries the chain, so the tinfo walk handles every
// shape the AST walker did.
export fn typeis8byteprim(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_PTR) { return true; };
if (k == tykind.TY_FN) { return true; };
if (k == tykind.TY_CHAN) { return true; };
if (k == tykind.TY_SLICE) { return false; };
if (k == tykind.TY_TUPLE) { return false; };
if (k == tykind.TY_TAGGED) { return false; };
if (k == tykind.TY_STR) { return false; };
if (k == tykind.TY_STRUCT) { return false; };
if (k == tykind.TY_ARRAY) { return t.size == 8u64; };
if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); };
// Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/
// int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init
// in cstage's `sz==8` branch.
return true;
};
export fn typeisuntyped(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
@@ -11118,72 +11206,13 @@ fn typenameisunsigned(nm: str) bool = {
};
// typeis8byteprimitive — does this type take exactly one 8-byte
// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive
// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET
// zero-init to mirror C cgen's "only zero if sz == 8 at the type
// level" rule. Strings (16), slices (24), tagged unions (>=16),
// tuples (16), structs (varies), arrays — all fall through to
// false here even when their *slot* rounds up to 8.
// slot rather than a wider aggregate? One-liner via typeis8byteprim
// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww
// L426-436 for every type-AST kind callers reach. Collapsed onto the
// tinfo helper per A.6.3b (#46).
fn typeis8byteprimitive(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return true; };
if (k == nkind.N_TFN) { return true; };
if (k == nkind.N_TCHAN) { return true; };
if (k == nkind.N_TSLICE) { return false; };
if (k == nkind.N_TARRAY) {
// C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever
// its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`,
// `[1]i64`. Mirror that here so the wwstage matches.
let lenn: *node = t.rhs;
let elemn: *node = t.lhs;
if (lenn == nil) { return false; };
if (lenn.kind != nkind.N_INTLIT) { return false; };
let elen: i64 = lenn.uval: i64;
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
return (esz: i64 * elen) == 8i64;
};
if (k == nkind.N_TTUPLE) { return false; };
if (k == nkind.N_TTAGGED){ return false; };
// STATUS-3 #22: `!T` carries the error flag on T's underlying
// shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's
// kind, just sets iserror). cstage's N_LET sizes off lu->kind,
// so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice`
// keep their composite slot. Defer to the inner type so
// `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls
// through to the multi-word fill.
if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); };
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return false; };
// Plain `void` slot: cstage sz=8 default → MOVQ $0. The let-
// decl is a phantom (a tagged-union variant tag carrier), but
// the slot is still 8B and zero-inits like any other prim.
if (streq(nm, "void")) { return true; };
// Struct alias: not a primitive even if the slot is 8B.
if (structlookup(c, nm) != nil) { return false; };
// Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...).
// All of these get slot-padded to 8 and zero-init in C.
if (primsize(nm) > 0) { return true; };
// STATUS-3 #22: alias to `!T` or to `void` (Hare-style error
// type / phantom variant). cstage resolves the alias and
// lands on sz=8 default. Follow through aliaslookup so
// `type invalid = !void;` and `type done = void;` zero-init.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) {
return typeis8byteprimitive(c, aliased);
};
};
return false;
};
return false;
return typeis8byteprim(t.type_: *tinfo);
};
// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`),
@@ -12419,54 +12448,20 @@ fn collectstructs(c: *cgen, file: *node) void = {
};
};
// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the
// alias chain registered at file load.
fn isstrtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return true; };
};
return false;
};
// isstrtype — alias-aware. Reads the stamped tinfo so `str`,
// `type alias = str`, `!str`, and chained aliases all route to the
// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT.
// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is
// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's
// TBANG-unwrap (check.ww:1145).
fn isstrtype(c: *cgen, t: *node) bool = {
if (isstrtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isstrtyperaw(r)) { return true; };
// `parserr = !str` — `!T` aliases shouldn't hide their
// underlying type from str-routing. Unwrap and re-check.
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (isstrtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (isstrtyperaw(r2)) { return true; };
};
};
};
return false;
};
fn isslicetyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TSLICE) { return true; };
return false;
return typeisstr(t.type_: *tinfo);
};
fn isslicetype(c: *cgen, t: *node) bool = {
if (isslicetyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isslicetyperaw(r);
};
fn istaggedtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TTAGGED) { return true; };
return false;
return typeisslice(t.type_: *tinfo);
};
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
@@ -12587,64 +12582,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = {
return si.totsize;
};
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
// underlying decl, then unwrap a leading N_TBANG so `type error =
// !(invalid | overflow);` is still recognised as tagged. Without the
// bang unwrap the prologue treats the param as scalar (8B), spilling
// only DI and losing the value-word SI; the match read of slot+8 then
// trails into saved BP.
// istaggedtype — alias-aware. Reads stamped tinfo so `T`,
// `type alias = (A|B)`, `type error = !(invalid|overflow)` all
// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap
// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c
// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46).
fn istaggedtype(c: *cgen, t: *node) bool = {
if (istaggedtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (istaggedtyperaw(r)) { return true; };
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (istaggedtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (istaggedtyperaw(r2)) { return true; };
};
};
};
return false;
};
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
fn isf32typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f32");
return typeistagged(t.type_: *tinfo);
};
fn isf64typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f64");
};
// isfloattype — f32 / f64 (and aliases of those). Used by cglet,
// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to
// dispatch the MOVSS/MOVSD-shaped paths.
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
// prologue. Collapsed per A.6.3b (#46).
export fn isfloattype(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (isf64typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isf32typeraw(r)) { return true; };
if (isf64typeraw(r)) { return true; };
return false;
if (t == nil) { return false; };
return typeisfloat(t.type_: *tinfo);
};
// isf32type — narrower predicate: true only for f32 (after alias
// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and
// the SS-variant arithmetic / cast opcodes.
// isf32type — narrower: true only for f32 (after alias chase). Cite
// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS-
// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46).
export fn isf32type(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isf32typeraw(r);
if (t == nil) { return false; };
return typeisf32(t.type_: *tinfo);
};
// exprfloatkind — classify an expression's value-class so callers can
@@ -12765,30 +12727,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = {
return 0;
};
// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and
// one `void`. Folds to a single 8-byte pointer slot per Hare's
// `(*T | null)` semantics. Mirrors check.c's resolve_type detection.
// isnullabletype — `(*T | void)` one-word fold per Hare's
// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`;
// the .nullable flag lands on tinfo at check.ww:1309-1318 when the
// two-variant shape matches. Collapsed per A.6.3b (#46).
export fn isnullabletype(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TTAGGED) { return false; };
let a: *node = t.list;
if (a == nil) { return false; };
let b: *node = a.next;
if (b == nil) { return false; };
if (b.next != nil) { return false; };
let aptr: bool = (a.kind == nkind.N_TPTR);
let bptr: bool = (b.kind == nkind.N_TPTR);
let avoid: bool = (a.kind == nkind.N_TNAME);
if (avoid) { avoid = streq(a.str, "void"); };
let bvoid: bool = (b.kind == nkind.N_TNAME);
if (bvoid) { bvoid = streq(b.str, "void"); };
if (aptr) { if (bvoid) { return true; }; };
if (avoid) { if (bptr) { return true; }; };
return false;
return typeisnullable(t.type_: *tinfo);
};
// nullableptrtag — 0-based index of the *T variant in a nullable
// union. The void variant takes the other slot (0 or 1).
// union. The void variant takes the other slot (0 or 1). AST-keyed
// for now: tinfofornode doesn't populate TY_TAGGED.params yet
// (check.ww:1287-1337 sets size/align/nullable but not the variant
// chain), so the tinfo equivalent of cstage cgen.c:405
// `nullable_ptr_tag` can't read params today. Graduates to a pure
// tinfo helper in A.6.3f (#50) alongside the variant-index work and
// the tparam-population glue.
export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
if (t.kind != nkind.N_TTAGGED) { return 0; };
@@ -15663,7 +15618,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
if (tp != nil) {
let tpt: *node = tp.lhs;
if (isstrtyperaw(tpt)) {
if (isstrtype(c, tpt)) {
emitline("\tMOVQ\t");
emitoff((lc.off + foff + 0): i64);
emitline("(BP), AX\n");
@@ -20714,8 +20669,8 @@ fn cglet(c: *cgen, n: *node) void = {
let p1t: *node = nil;
if (p0 != nil) { p0t = p0.lhs; };
if (p1 != nil) { p1t = p1.lhs; };
let s0_is_str: bool = isstrtyperaw(p0t);
let s1_is_str: bool = isstrtyperaw(p1t);
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
@@ -21257,8 +21212,8 @@ fn cgmlet(c: *cgen, n: *node) void = {
if (t0 == nil) { t0 = p0t; };
if (t1 == nil) { t1 = p1t; };
let s0_is_str: bool = isstrtyperaw(t0);
let s1_is_str: bool = isstrtyperaw(t1);
let s0_is_str: bool = isstrtype(c, t0);
let s1_is_str: bool = isstrtype(c, t1);
cgexpr(c, rhs);

View File

@@ -1485,7 +1485,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
if (tp != nil) {
let tpt: *node = tp.lhs;
if (isstrtyperaw(tpt)) {
if (isstrtype(c, tpt)) {
emitline("\tMOVQ\t");
emitoff((lc.off + foff + 0): i64);
emitline("(BP), AX\n");

View File

@@ -712,8 +712,8 @@ fn cglet(c: *cgen, n: *node) void = {
let p1t: *node = nil;
if (p0 != nil) { p0t = p0.lhs; };
if (p1 != nil) { p1t = p1.lhs; };
let s0_is_str: bool = isstrtyperaw(p0t);
let s1_is_str: bool = isstrtyperaw(p1t);
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
@@ -1255,8 +1255,8 @@ fn cgmlet(c: *cgen, n: *node) void = {
if (t0 == nil) { t0 = p0t; };
if (t1 == nil) { t1 = p1t; };
let s0_is_str: bool = isstrtyperaw(t0);
let s1_is_str: bool = isstrtyperaw(t1);
let s0_is_str: bool = isstrtype(c, t0);
let s1_is_str: bool = isstrtype(c, t1);
cgexpr(c, rhs);

View File

@@ -867,72 +867,13 @@ fn typenameisunsigned(nm: str) bool = {
};
// typeis8byteprimitive — does this type take exactly one 8-byte
// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive
// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET
// zero-init to mirror C cgen's "only zero if sz == 8 at the type
// level" rule. Strings (16), slices (24), tagged unions (>=16),
// tuples (16), structs (varies), arrays — all fall through to
// false here even when their *slot* rounds up to 8.
// slot rather than a wider aggregate? One-liner via typeis8byteprim
// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww
// L426-436 for every type-AST kind callers reach. Collapsed onto the
// tinfo helper per A.6.3b (#46).
fn typeis8byteprimitive(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return true; };
if (k == nkind.N_TFN) { return true; };
if (k == nkind.N_TCHAN) { return true; };
if (k == nkind.N_TSLICE) { return false; };
if (k == nkind.N_TARRAY) {
// C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever
// its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`,
// `[1]i64`. Mirror that here so the wwstage matches.
let lenn: *node = t.rhs;
let elemn: *node = t.lhs;
if (lenn == nil) { return false; };
if (lenn.kind != nkind.N_INTLIT) { return false; };
let elen: i64 = lenn.uval: i64;
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
return (esz: i64 * elen) == 8i64;
};
if (k == nkind.N_TTUPLE) { return false; };
if (k == nkind.N_TTAGGED){ return false; };
// STATUS-3 #22: `!T` carries the error flag on T's underlying
// shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's
// kind, just sets iserror). cstage's N_LET sizes off lu->kind,
// so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice`
// keep their composite slot. Defer to the inner type so
// `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls
// through to the multi-word fill.
if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); };
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return false; };
// Plain `void` slot: cstage sz=8 default → MOVQ $0. The let-
// decl is a phantom (a tagged-union variant tag carrier), but
// the slot is still 8B and zero-inits like any other prim.
if (streq(nm, "void")) { return true; };
// Struct alias: not a primitive even if the slot is 8B.
if (structlookup(c, nm) != nil) { return false; };
// Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...).
// All of these get slot-padded to 8 and zero-init in C.
if (primsize(nm) > 0) { return true; };
// STATUS-3 #22: alias to `!T` or to `void` (Hare-style error
// type / phantom variant). cstage resolves the alias and
// lands on sz=8 default. Follow through aliaslookup so
// `type invalid = !void;` and `type done = void;` zero-init.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) {
return typeis8byteprimitive(c, aliased);
};
};
return false;
};
return false;
return typeis8byteprim(t.type_: *tinfo);
};
// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`),
@@ -2168,54 +2109,20 @@ fn collectstructs(c: *cgen, file: *node) void = {
};
};
// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the
// alias chain registered at file load.
fn isstrtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return true; };
};
return false;
};
// isstrtype — alias-aware. Reads the stamped tinfo so `str`,
// `type alias = str`, `!str`, and chained aliases all route to the
// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT.
// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is
// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's
// TBANG-unwrap (check.ww:1145).
fn isstrtype(c: *cgen, t: *node) bool = {
if (isstrtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isstrtyperaw(r)) { return true; };
// `parserr = !str` — `!T` aliases shouldn't hide their
// underlying type from str-routing. Unwrap and re-check.
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (isstrtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (isstrtyperaw(r2)) { return true; };
};
};
};
return false;
};
fn isslicetyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TSLICE) { return true; };
return false;
return typeisstr(t.type_: *tinfo);
};
fn isslicetype(c: *cgen, t: *node) bool = {
if (isslicetyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isslicetyperaw(r);
};
fn istaggedtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TTAGGED) { return true; };
return false;
return typeisslice(t.type_: *tinfo);
};
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
@@ -2336,64 +2243,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = {
return si.totsize;
};
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
// underlying decl, then unwrap a leading N_TBANG so `type error =
// !(invalid | overflow);` is still recognised as tagged. Without the
// bang unwrap the prologue treats the param as scalar (8B), spilling
// only DI and losing the value-word SI; the match read of slot+8 then
// trails into saved BP.
// istaggedtype — alias-aware. Reads stamped tinfo so `T`,
// `type alias = (A|B)`, `type error = !(invalid|overflow)` all
// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap
// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c
// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46).
fn istaggedtype(c: *cgen, t: *node) bool = {
if (istaggedtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (istaggedtyperaw(r)) { return true; };
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (istaggedtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (istaggedtyperaw(r2)) { return true; };
};
};
};
return false;
};
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
fn isf32typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f32");
return typeistagged(t.type_: *tinfo);
};
fn isf64typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f64");
};
// isfloattype — f32 / f64 (and aliases of those). Used by cglet,
// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to
// dispatch the MOVSS/MOVSD-shaped paths.
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
// prologue. Collapsed per A.6.3b (#46).
export fn isfloattype(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (isf64typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isf32typeraw(r)) { return true; };
if (isf64typeraw(r)) { return true; };
return false;
if (t == nil) { return false; };
return typeisfloat(t.type_: *tinfo);
};
// isf32type — narrower predicate: true only for f32 (after alias
// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and
// the SS-variant arithmetic / cast opcodes.
// isf32type — narrower: true only for f32 (after alias chase). Cite
// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS-
// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46).
export fn isf32type(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isf32typeraw(r);
if (t == nil) { return false; };
return typeisf32(t.type_: *tinfo);
};
// exprfloatkind — classify an expression's value-class so callers can
@@ -2514,30 +2388,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = {
return 0;
};
// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and
// one `void`. Folds to a single 8-byte pointer slot per Hare's
// `(*T | null)` semantics. Mirrors check.c's resolve_type detection.
// isnullabletype — `(*T | void)` one-word fold per Hare's
// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`;
// the .nullable flag lands on tinfo at check.ww:1309-1318 when the
// two-variant shape matches. Collapsed per A.6.3b (#46).
export fn isnullabletype(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TTAGGED) { return false; };
let a: *node = t.list;
if (a == nil) { return false; };
let b: *node = a.next;
if (b == nil) { return false; };
if (b.next != nil) { return false; };
let aptr: bool = (a.kind == nkind.N_TPTR);
let bptr: bool = (b.kind == nkind.N_TPTR);
let avoid: bool = (a.kind == nkind.N_TNAME);
if (avoid) { avoid = streq(a.str, "void"); };
let bvoid: bool = (b.kind == nkind.N_TNAME);
if (bvoid) { bvoid = streq(b.str, "void"); };
if (aptr) { if (bvoid) { return true; }; };
if (avoid) { if (bptr) { return true; }; };
return false;
return typeisnullable(t.type_: *tinfo);
};
// nullableptrtag — 0-based index of the *T variant in a nullable
// union. The void variant takes the other slot (0 or 1).
// union. The void variant takes the other slot (0 or 1). AST-keyed
// for now: tinfofornode doesn't populate TY_TAGGED.params yet
// (check.ww:1287-1337 sets size/align/nullable but not the variant
// chain), so the tinfo equivalent of cstage cgen.c:405
// `nullable_ptr_tag` can't read params today. Graduates to a pure
// tinfo helper in A.6.3f (#50) alongside the variant-index work and
// the tparam-population glue.
export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
if (t.kind != nkind.N_TTAGGED) { return 0; };

View File

@@ -6666,6 +6666,94 @@ export fn typeissigned(t: *tinfo) bool = {
return typeisint(t);
};
// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default).
// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts
// the same untyped form. ww walks the .under chain so alias-of-alias
// (`type s2 = s1; type s1 = str;`) lands the same way.
export fn typeisstr(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_STR) { return true; };
if (k == tykind.TY_UNTYPED_STR) { return true; };
if (k == tykind.TY_NAMED) { return typeisstr(t.under); };
return false;
};
// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`.
export fn typeisslice(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_SLICE) { return true; };
if (k == tykind.TY_NAMED) { return typeisslice(t.under); };
return false;
};
// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516
// `type_istagged` — same single-peel shape. The node-keyed wwstage
// helper this replaces also unwrapped a leading N_TBANG so
// `type error = !(invalid | overflow);` registered as tagged. Post-
// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145-
// 1152 returns the inner tinfo unchanged) so we recover the cstage
// semantics with the bare kind check + NAMED chase.
export fn typeistagged(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return true; };
if (k == tykind.TY_NAMED) { return typeistagged(t.under); };
return false;
};
// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias
// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs
// MOVSD and the SS-variant arithmetic / cast opcodes.
export fn typeisf32(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_F32) { return true; };
if (k == tykind.TY_NAMED) { return typeisf32(t.under); };
return false;
};
// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold.
// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is
// stamped by tinfofornode (check.ww:1309-1318) when the two-variant
// shape matches.
export fn typeisnullable(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_TAGGED) { return t.nullable != 0; };
if (k == tykind.TY_NAMED) { return typeisnullable(t.under); };
return false;
};
// typeis8byteprim — does this type take exactly one 8-byte stack
// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to
// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate?
// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8`
// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage
// helper this replaces predates tinfo and AST-walked TBANG / TNAME
// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and
// TY_NAMED.under carries the chain, so the tinfo walk handles every
// shape the AST walker did.
export fn typeis8byteprim(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_PTR) { return true; };
if (k == tykind.TY_FN) { return true; };
if (k == tykind.TY_CHAN) { return true; };
if (k == tykind.TY_SLICE) { return false; };
if (k == tykind.TY_TUPLE) { return false; };
if (k == tykind.TY_TAGGED) { return false; };
if (k == tykind.TY_STR) { return false; };
if (k == tykind.TY_STRUCT) { return false; };
if (k == tykind.TY_ARRAY) { return t.size == 8u64; };
if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); };
// Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/
// int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init
// in cstage's `sz==8` branch.
return true;
};
export fn typeisuntyped(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
@@ -11118,72 +11206,13 @@ fn typenameisunsigned(nm: str) bool = {
};
// typeis8byteprimitive — does this type take exactly one 8-byte
// slot (pointer / fn-ptr / 64-bit int / chan / scalar primitive
// padded up to 8) rather than a wider aggregate? Used by nkind.N_LET
// zero-init to mirror C cgen's "only zero if sz == 8 at the type
// level" rule. Strings (16), slices (24), tagged unions (>=16),
// tuples (16), structs (varies), arrays — all fall through to
// false here even when their *slot* rounds up to 8.
// slot rather than a wider aggregate? One-liner via typeis8byteprim
// (cstage N_LET sz==8 ladder SSoT). t.type_ is stamped at check.ww
// L426-436 for every type-AST kind callers reach. Collapsed onto the
// tinfo helper per A.6.3b (#46).
fn typeis8byteprimitive(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let k: nkind = t.kind;
if (k == nkind.N_TPTR) { return true; };
if (k == nkind.N_TFN) { return true; };
if (k == nkind.N_TCHAN) { return true; };
if (k == nkind.N_TSLICE) { return false; };
if (k == nkind.N_TARRAY) {
// C cgen (cmd/w6c/cgen.c:3317) zero-inits TY_ARRAY whenever
// its raw byte size is 8 — e.g. `[8]bool`, `[2]i32`, `[4]i16`,
// `[1]i64`. Mirror that here so the wwstage matches.
let lenn: *node = t.rhs;
let elemn: *node = t.lhs;
if (lenn == nil) { return false; };
if (lenn.kind != nkind.N_INTLIT) { return false; };
let elen: i64 = lenn.uval: i64;
let esz: i32 = 8;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
return (esz: i64 * elen) == 8i64;
};
if (k == nkind.N_TTUPLE) { return false; };
if (k == nkind.N_TTAGGED){ return false; };
// STATUS-3 #22: `!T` carries the error flag on T's underlying
// shape (cmd/wcc/check.c:290 resolve_type N_TBANG copies T's
// kind, just sets iserror). cstage's N_LET sizes off lu->kind,
// so `!void`/`!i32` land in the sz=8 default and `!str`/`!slice`
// keep their composite slot. Defer to the inner type so
// `let e: !void;` mirrors cstage's MOVQ $0 while `!str` falls
// through to the multi-word fill.
if (k == nkind.N_TBANG) { return typeis8byteprimitive(c, t.lhs); };
if (k == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return false; };
// Plain `void` slot: cstage sz=8 default → MOVQ $0. The let-
// decl is a phantom (a tagged-union variant tag carrier), but
// the slot is still 8B and zero-inits like any other prim.
if (streq(nm, "void")) { return true; };
// Struct alias: not a primitive even if the slot is 8B.
if (structlookup(c, nm) != nil) { return false; };
// Primitive (i8/u8/.../i64/u64/bool/rune/f32/f64/int/...).
// All of these get slot-padded to 8 and zero-init in C.
if (primsize(nm) > 0) { return true; };
// STATUS-3 #22: alias to `!T` or to `void` (Hare-style error
// type / phantom variant). cstage resolves the alias and
// lands on sz=8 default. Follow through aliaslookup so
// `type invalid = !void;` and `type done = void;` zero-init.
if (c != nil) {
let aliased: *node = aliaslookup(c, nm);
if (aliased != nil) {
return typeis8byteprimitive(c, aliased);
};
};
return false;
};
return false;
return typeis8byteprim(t.type_: *tinfo);
};
// elemissignedc — given an indexable type-AST (`*T`, `[]T`, `[N]T`),
@@ -12419,54 +12448,20 @@ fn collectstructs(c: *cgen, file: *node) void = {
};
};
// `type X = str;` aliases) to `str`. Takes *cgen so it can walk the
// alias chain registered at file load.
fn isstrtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TNAME) {
let nm: str = t.str;
if (streq(nm, "str")) { return true; };
};
return false;
};
// isstrtype — alias-aware. Reads the stamped tinfo so `str`,
// `type alias = str`, `!str`, and chained aliases all route to the
// str-shaped slot. Cite cstage cgen.c:159 `type_isstr` SSoT.
// Collapsed onto typeisstr per A.6.3b (#46); the prior AST walker is
// reconstituted by typeisstr's TY_NAMED chase + tinfofornode's
// TBANG-unwrap (check.ww:1145).
fn isstrtype(c: *cgen, t: *node) bool = {
if (isstrtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isstrtyperaw(r)) { return true; };
// `parserr = !str` — `!T` aliases shouldn't hide their
// underlying type from str-routing. Unwrap and re-check.
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (isstrtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (isstrtyperaw(r2)) { return true; };
};
};
};
return false;
};
fn isslicetyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TSLICE) { return true; };
return false;
return typeisstr(t.type_: *tinfo);
};
fn isslicetype(c: *cgen, t: *node) bool = {
if (isslicetyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isslicetyperaw(r);
};
fn istaggedtyperaw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == nkind.N_TTAGGED) { return true; };
return false;
return typeisslice(t.type_: *tinfo);
};
// resolvetagged — return the underlying N_TTAGGED node for `t`, or nil
@@ -12587,64 +12582,31 @@ fn structparamsize(c: *cgen, t: *node) i32 = {
return si.totsize;
};
// istaggedtype — alias-aware. Mirrors isstrtype: follow N_TNAME to its
// underlying decl, then unwrap a leading N_TBANG so `type error =
// !(invalid | overflow);` is still recognised as tagged. Without the
// bang unwrap the prologue treats the param as scalar (8B), spilling
// only DI and losing the value-word SI; the match read of slot+8 then
// trails into saved BP.
// istaggedtype — alias-aware. Reads stamped tinfo so `T`,
// `type alias = (A|B)`, `type error = !(invalid|overflow)` all
// resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap
// (check.ww:1145) so we don't re-walk it here. Cite cstage cgen.c
// (`type_chase_named` + TY_TAGGED). Collapsed per A.6.3b (#46).
fn istaggedtype(c: *cgen, t: *node) bool = {
if (istaggedtyperaw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (istaggedtyperaw(r)) { return true; };
if (r != nil) {
if (r.kind == nkind.N_TBANG) {
let inner: *node = r.lhs;
if (istaggedtyperaw(inner)) { return true; };
if (inner != nil) {
let r2: *node = resolvetype(c, inner);
if (istaggedtyperaw(r2)) { return true; };
};
};
};
return false;
};
// isf32typeraw / isf64typeraw — bare TNAME check, no alias resolution.
fn isf32typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f32");
return typeistagged(t.type_: *tinfo);
};
fn isf64typeraw(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TNAME) { return false; };
return streq(t.str, "f64");
};
// isfloattype — f32 / f64 (and aliases of those). Used by cglet,
// cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-prologue to
// dispatch the MOVSS/MOVSD-shaped paths.
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
// prologue. Collapsed per A.6.3b (#46).
export fn isfloattype(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (isf64typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
if (isf32typeraw(r)) { return true; };
if (isf64typeraw(r)) { return true; };
return false;
if (t == nil) { return false; };
return typeisfloat(t.type_: *tinfo);
};
// isf32type — narrower predicate: true only for f32 (after alias
// resolution). f64 returns false. Used to pick MOVSS vs MOVSD and
// the SS-variant arithmetic / cast opcodes.
// isf32type — narrower: true only for f32 (after alias chase). Cite
// cstage cgen.c:188 `type_isf32`. Picks MOVSS vs MOVSD and the SS-
// variant arithmetic / cast opcodes. Collapsed per A.6.3b (#46).
export fn isf32type(c: *cgen, t: *node) bool = {
if (isf32typeraw(t)) { return true; };
if (c == nil) { return false; };
let r: *node = resolvetype(c, t);
return isf32typeraw(r);
if (t == nil) { return false; };
return typeisf32(t.type_: *tinfo);
};
// exprfloatkind — classify an expression's value-class so callers can
@@ -12765,30 +12727,23 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = {
return 0;
};
// isnullabletype — nkind.N_TTAGGED with exactly two children, one *T and
// one `void`. Folds to a single 8-byte pointer slot per Hare's
// `(*T | null)` semantics. Mirrors check.c's resolve_type detection.
// isnullabletype — `(*T | void)` one-word fold per Hare's
// `(*T | null)` semantics. Cite cstage cgen.c:396 `type_isnullable`;
// the .nullable flag lands on tinfo at check.ww:1309-1318 when the
// two-variant shape matches. Collapsed per A.6.3b (#46).
export fn isnullabletype(t: *node) bool = {
if (t == nil) { return false; };
if (t.kind != nkind.N_TTAGGED) { return false; };
let a: *node = t.list;
if (a == nil) { return false; };
let b: *node = a.next;
if (b == nil) { return false; };
if (b.next != nil) { return false; };
let aptr: bool = (a.kind == nkind.N_TPTR);
let bptr: bool = (b.kind == nkind.N_TPTR);
let avoid: bool = (a.kind == nkind.N_TNAME);
if (avoid) { avoid = streq(a.str, "void"); };
let bvoid: bool = (b.kind == nkind.N_TNAME);
if (bvoid) { bvoid = streq(b.str, "void"); };
if (aptr) { if (bvoid) { return true; }; };
if (avoid) { if (bptr) { return true; }; };
return false;
return typeisnullable(t.type_: *tinfo);
};
// nullableptrtag — 0-based index of the *T variant in a nullable
// union. The void variant takes the other slot (0 or 1).
// union. The void variant takes the other slot (0 or 1). AST-keyed
// for now: tinfofornode doesn't populate TY_TAGGED.params yet
// (check.ww:1287-1337 sets size/align/nullable but not the variant
// chain), so the tinfo equivalent of cstage cgen.c:405
// `nullable_ptr_tag` can't read params today. Graduates to a pure
// tinfo helper in A.6.3f (#50) alongside the variant-index work and
// the tparam-population glue.
export fn nullableptrtag(t: *node) i32 = {
if (t == nil) { return 0; };
if (t.kind != nkind.N_TTAGGED) { return 0; };
@@ -15663,7 +15618,7 @@ fn cgdot(c: *cgen, n: *node) void = {
};
if (tp != nil) {
let tpt: *node = tp.lhs;
if (isstrtyperaw(tpt)) {
if (isstrtype(c, tpt)) {
emitline("\tMOVQ\t");
emitoff((lc.off + foff + 0): i64);
emitline("(BP), AX\n");
@@ -20714,8 +20669,8 @@ fn cglet(c: *cgen, n: *node) void = {
let p1t: *node = nil;
if (p0 != nil) { p0t = p0.lhs; };
if (p1 != nil) { p1t = p1.lhs; };
let s0_is_str: bool = isstrtyperaw(p0t);
let s1_is_str: bool = isstrtyperaw(p1t);
let s0_is_str: bool = isstrtype(c, p0t);
let s1_is_str: bool = isstrtype(c, p1t);
if (p0 != nil) {
if (p1 != nil) {
if (s0_is_str != s1_is_str) {
@@ -21257,8 +21212,8 @@ fn cgmlet(c: *cgen, n: *node) void = {
if (t0 == nil) { t0 = p0t; };
if (t1 == nil) { t1 = p1t; };
let s0_is_str: bool = isstrtyperaw(t0);
let s1_is_str: bool = isstrtyperaw(t1);
let s0_is_str: bool = isstrtype(c, t0);
let s1_is_str: bool = isstrtype(c, t1);
cgexpr(c, rhs);