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

@@ -89,7 +89,7 @@ fn aliaslookup(c: *cgen, name: str) *node = {
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
let k: nkind = e.kind;
if (k == nkind.N_INTLIT) { *out = e.uval; return true; };
if (k == nkind.N_RUNELIT) { *out = e.uval; return true; };
if (k == nkind.N_TRUE) { *out = 1u64; return true; };
@@ -110,7 +110,7 @@ fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
let b: u64;
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
let op: i32 = e.op;
let op: tkind = e.op;
if (op == tkind.TK_PLUS) { *out = a + b; return true; };
if (op == tkind.TK_MINUS) { *out = a - b; return true; };
if (op == tkind.TK_STAR) { *out = a * b; return true; };
@@ -132,7 +132,7 @@ fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (k == nkind.N_UN) {
let v: u64;
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
let op: i32 = e.op;
let op: tkind = e.op;
if (op == tkind.TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == tkind.TK_TILDE) { *out = ~v; return true; };
if (op == tkind.TK_PLUS) { *out = v; return true; };

View File

@@ -278,8 +278,11 @@ export fn cgfile(c: *cgen, file: *node) void = {
c.strlits = nil;
c.strlitseq = 0;
collectaliases(c, file);
collectstructs(c, file);
// Enums must register before structs — fieldsize on a tkind-typed
// field needs the enum's storage size, otherwise it falls back to
// 8 (wrong load width).
collectenums(c, file);
collectstructs(c, file);
collectdefs(c, file);
collectfnrets(c, file);
fficollect(c, file);

View File

@@ -22,7 +22,7 @@ use strconv;
fn cgexpr(c: *cgen, n: *node) void = {
if (n == nil) { return; };
let k: i32 = n.kind;
let k: nkind = n.kind;
if (k == nkind.N_INTLIT) {
// Print signed (i64), not unsigned (u64). C cgen uses
@@ -257,7 +257,7 @@ fn cgtypetest(c: *cgen, n: *node) void = {
// through the cast pass-through too).
fn isenumexpr(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
let k: nkind = e.kind;
if (k == nkind.N_DOT) {
if (e.lhs != nil) {
if (e.lhs.kind == nkind.N_IDENT) {
@@ -650,7 +650,7 @@ fn cgdot(c: *cgen, n: *node) void = {
let lc: *local = localfindnode(c, nm);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: i32 = -1;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
// Pointer-to-struct: deref then field load.
if (lkind == nkind.N_TPTR) {
@@ -807,7 +807,7 @@ fn cgdot(c: *cgen, n: *node) void = {
// pointed-to header. C cgen does the same.
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
let innerkind: i32 = -1;
let innerkind: nkind = nkind.N_NONE;
if (inner != nil) { innerkind = inner.kind; };
let innerstr: bool = false;
if (innerkind == nkind.N_TNAME) {
@@ -1071,7 +1071,7 @@ fn cgcall(c: *cgen, n: *node) void = {
if (lc != nil) {
let tn: *node = lc.tnode;
if (tn != nil) {
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; };
@@ -1291,7 +1291,7 @@ fn cgassign(c: *cgen, n: *node) void = {
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; };
// Pointer-to-struct: deref then store.
if (lkind == nkind.N_TPTR) {
@@ -1397,7 +1397,7 @@ fn cgassign(c: *cgen, n: *node) void = {
if (delta >= 0) {
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
let innerkind: i32 = -1;
let innerkind: nkind = nkind.N_NONE;
if (inner != nil) { innerkind = inner.kind; };
let innerstr: bool = false;
if (innerkind == nkind.N_TNAME) {

View File

@@ -20,7 +20,7 @@ use strconv;
fn cgstmt(c: *cgen, n: *node) void = {
if (n == nil) { return; };
let k: i32 = n.kind;
let k: nkind = n.kind;
if (k == nkind.N_BLOCK) { cgblock(c, n); return; };

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; };

View File

@@ -68,7 +68,7 @@ fn seedprimitives(c: *checker) void = {
// the name so forward references resolve.
fn installdecl(c: *checker, d: *node) void = {
if (d == nil) { return; };
let k: i32 = d.kind;
let k: nkind = d.kind;
let nm: str = d.str;
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
if (k == nkind.N_DEF) { scopedefine(c.top, nm, skind.SK_DEF, nil, d); return; };
@@ -87,7 +87,7 @@ fn installdecl(c: *checker, d: *node) void = {
// the same pass — they need the same scope state.
fn resolvewalk(c: *checker, n: *node) void = {
if (n == nil) { return; };
let k: i32 = n.kind;
let k: nkind = n.kind;
// Typed checks fire on the way down so the scrutinee/operand
// is examined before the arm bodies install new bindings.
@@ -253,7 +253,7 @@ fn typeeqast(a: *node, b: *node) bool = {
if (aa == nil) { return bb == nil; };
if (bb == nil) { return false; };
if (aa.kind != bb.kind) { return false; };
let k: i32 = aa.kind;
let k: nkind = aa.kind;
if (k == nkind.N_TNAME) { return streq(aa.str, bb.str); };
if (k == nkind.N_TPTR) { return typeeqast(aa.lhs, bb.lhs); };
if (k == nkind.N_TSLICE){ return typeeqast(aa.lhs, bb.lhs); };
@@ -342,7 +342,7 @@ fn mktname(c: *checker, nm: str) *node = {
// field access into non-primitive types, etc).
fn exprtype(c: *checker, e: *node) *node = {
if (e == nil) { return nil; };
let k: i32 = e.kind;
let k: nkind = e.kind;
if (k == nkind.N_INTLIT) { return mktname(c, "untyped_int"); };
if (k == nkind.N_FLOATLIT) { return mktname(c, "untyped_float"); };
if (k == nkind.N_STRLIT) { return mktname(c, "str"); };
@@ -900,7 +900,7 @@ export fn checkfile(c: *checker, file: *node) void = {
// Pass 2: walk decl bodies/types and resolve identifiers.
d = file.list;
for (d != nil) {
let k: i32 = d.kind;
let k: nkind = d.kind;
if (k == nkind.N_FNDECL) {
if (d.lhs != nil) { resolvewalk(c, d.lhs); }; // return type
resolvefnbody(c, d);