ww+wcc: Hare-strict enum types — back out the int↔enum relaxation

Cascades the four enum kinds through every signature and local that
holds one of their values, then removes the type_assignable /
unify_arith relaxation that previously let bare i32 mix with the
named enum types.

Signature updates:
  - kwlookup() now returns `tkind` (not i32); tokname() takes `tkind`
  - accepttok / expecttok / bprec / isassignop take `tkind`
  - parsearglist's closekind is `tkind`
  - newtype / prim take `tykind`; scopedefine takes `skind`
  - newnode / nkname take `nkind`

Struct fields:
  - tok.kind is `tkind`; parser.curkind is `tkind`
  - node.kind is `nkind`; node.op is `tkind`
  - tinfo.kind is `tykind`; sym.skind is `skind`

Locals holding kinds across lex/parse/check/cgen are now typed with
their enum, including sentinel patterns like `let lkind: nkind =
nkind.N_NONE; if (...) lkind = tn.kind;`.

The selfhost cgen had a load-width bug exposed by this: fieldsize()
fell back to 8 bytes for any TNAME that wasn't a struct or primitive.
For a tkind-typed field that gave `MOVQ (BX), AX` instead of `MOVL`,
diverging from the C cgen on tok.kind / parser.curkind / etc. Two
fixes:
  - fieldsize now consults the enum registry and returns the storage
    type's size (4 for `enum i32`)
  - collectenums runs before collectstructs in cgfile so the registry
    is populated when registerstruct asks for field sizes

All 22 tests stay green; 990/993/995 byte-identity probes pass with
the strict typing in place.
This commit is contained in:
2026-05-12 05:04:33 +09:00
parent 3affe01705
commit 922877309b
17 changed files with 210 additions and 194 deletions

View File

@@ -153,7 +153,7 @@ fn pushargsrev(c: *cgen, arg: *node) i32 = {
fn nodeisslice(c: *cgen, n: *node) bool = {
if (n == nil) { return false; };
let k: i32 = n.kind;
let k: nkind = n.kind;
if (k == nkind.N_IDENT) {
let nm: str = n.str;
let lc: *local = localfindnode(c, nm);
@@ -169,7 +169,7 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
// (str args take two slots: ptr + len).
fn nodeisstr(c: *cgen, n: *node) bool = {
if (n == nil) { return false; };
let k: i32 = n.kind;
let k: nkind = n.kind;
if (k == nkind.N_STRLIT) { return true; };
if (k == nkind.N_IDENT) {
let nm: str = n.str;
@@ -210,7 +210,7 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: i32 = -1;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
if (lkind == nkind.N_TNAME) { sname = tn.str; };
if (lkind == nkind.N_TPTR) {
@@ -279,7 +279,7 @@ fn typenodeisunsigned(t: *node) bool = {
// false here even when their *slot* rounds up to 8.
fn typeis8byteprimitive(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
let k: i32 = t.kind;
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; };
@@ -371,7 +371,7 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
};
// Generic struct field: if it's *T, element size is T's size.
let lkind: i32 = tn.kind;
let lkind: nkind = tn.kind;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (lkind == nkind.N_TNAME) { sname = tn.str; };
@@ -470,7 +470,7 @@ fn dotinnerstructptr(c: *cgen, n: *node) *node = {
// bool/str-byte, 8 otherwise — same shape as C cgen's esz fallback).
fn elemsizeof(t: *node) i32 = {
if (t == nil) { return 1; };
let k: i32 = t.kind;
let k: nkind = t.kind;
let elem: *node = nil;
if (k == nkind.N_TPTR) { elem = t.lhs; };
if (k == nkind.N_TSLICE) { elem = t.lhs; };
@@ -506,7 +506,7 @@ fn elemsizeof(t: *node) i32 = {
// being wrong here is byte-different asm vs C, not bad runtime.
fn nodeisunsigned(c: *cgen, n: *node) bool = {
if (n == nil) { return false; };
let k: i32 = n.kind;
let k: nkind = n.kind;
if (k == nkind.N_IDENT) {
let nm: str = n.str;
let lc: *local = localfindnode(c, nm);
@@ -522,7 +522,7 @@ fn nodeisunsigned(c: *cgen, n: *node) bool = {
let lc: *local = localfindnode(c, bn);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: i32 = -1;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
let sname: str;
sname.ptr = nil; sname.len = 0;
@@ -684,7 +684,7 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
fn slotsize(c: *cgen, typn: *node) i32 = {
if (typn == nil) { return 8; };
let k: i32 = typn.kind;
let k: nkind = typn.kind;
if (k == nkind.N_TPTR) { return 8; };
if (k == nkind.N_TFN) { return 8; };
if (k == nkind.N_TCHAN) { return 8; };
@@ -771,7 +771,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
// only round to 8 for stack slots, not struct interiors).
fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (tnode == nil) { return 8; };
let k: i32 = tnode.kind;
let k: nkind = tnode.kind;
if (k == nkind.N_TNAME) {
let nm: str = tnode.str;
if (streq(nm, "str")) { return 16; };
@@ -779,6 +779,18 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
if (ps > 0) { return ps; };
let si: *structinfo = structlookup(c, nm);
if (si != nil) { return si.totsize; };
// Enum: size of its storage type. Mirrors the C cgen, which
// reads Type.size off the TY_ENUM (which inherits from .sub).
let en: *enumtype = enumlookup(c, nm);
if (en != nil) {
if (en.storage != nil) {
if (en.storage.kind == nkind.N_TNAME) {
let sps: i32 = primsize(en.storage.str);
if (sps > 0) { return sps; };
};
};
return 4; // default storage is i32
};
return 8;
};
if (k == nkind.N_TPTR) { return 8; };