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

@@ -103,11 +103,11 @@ type nkind = enum i32 {
// ---- Node -------------------------------------------------------------
type node = struct {
kind: i32,
kind: nkind,
file: str,
line: i32,
col: i32,
op: i32, // for nkind.N_BIN / nkind.N_UN / nkind.N_ASSIGN
op: tkind, // for nkind.N_BIN / nkind.N_UN / nkind.N_ASSIGN
str: str,
uval: u64,
fval: f64,
@@ -125,7 +125,7 @@ type node = struct {
module: str, // originating module from `// MODULE: foo`; "" if none
};
export fn newnode(a: *arena, k: i32, file: str, line: i32, col: i32) *node = {
export fn newnode(a: *arena, k: nkind, file: str, line: i32, col: i32) *node = {
let n: *node = amalloc(a, 208u64): *node; // ≥ struct size
n.kind = k;
n.file = file;
@@ -136,7 +136,7 @@ export fn newnode(a: *arena, k: i32, file: str, line: i32, col: i32) *node = {
// ---- printer ----------------------------------------------------------
fn nkname(k: i32) str = {
fn nkname(k: nkind) str = {
if (k == nkind.N_NONE) { return "none"; };
if (k == nkind.N_INTLIT) { return "int"; };
if (k == nkind.N_FLOATLIT) { return "float"; };

View File

@@ -460,7 +460,7 @@ fn lexident(l: *lex, start: *pos, out: *tok) void = {
return;
};
};
let k: i32 = kwlookup(p, n: i32);
let k: tkind = kwlookup(p, n: i32);
if (k != tkind.TK_NONE) {
out.kind = k;
} else {
@@ -553,7 +553,7 @@ fn lexrune(l: *lex, start: *pos, out: *tok) void = {
out.uval = ch: u64;
};
fn emitsimple(start: *pos, k: i32, out: *tok) void = {
fn emitsimple(start: *pos, k: tkind, out: *tok) void = {
out.kind = k;
out.file = start.file;
out.line = start.line;

View File

@@ -134,8 +134,7 @@ type pos = struct {
};
type tok = struct {
kind: i32, // holds a `tkind` value; bare i32 so the cgen's
// fixed 8B/struct-field layout matches the C side
kind: tkind,
file: str, // path of the source the token came from
line: i32,
col: i32,
@@ -160,7 +159,7 @@ fn streqn(a: *u8, b: str, n: i32) bool = {
// kwlookup — returns the matching TK_* keyword kind for a byte run,
// or tkind.TK_NONE if it's an ordinary identifier. Linear search over a
// small alphabetised list, matching cmd/wcc/tok.c.
export fn kwlookup(p: *u8, n: i32) i32 = {
export fn kwlookup(p: *u8, n: i32) tkind = {
if (streqn(p, "as", n)) { return tkind.TK_AS; };
if (streqn(p, "break", n)) { return tkind.TK_BREAK; };
if (streqn(p, "case", n)) { return tkind.TK_CASE; };
@@ -198,7 +197,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
// Returns the canonical printable spelling for a token kind. Matches
// the C tokname()'s output exactly so wwdump output diffs cleanly.
export fn tokname(k: i32) str = {
export fn tokname(k: tkind) str = {
if (k == tkind.TK_NONE) { return "<none>"; };
if (k == tkind.TK_EOF) { return "EOF"; };
if (k == tkind.TK_ERR) { return "ERR"; };

View File

@@ -199,7 +199,7 @@ fn parseprimary(p: *parser) *node = {
return newnode(p.a, nkind.N_NONE, pf, pl, pc);
};
fn parsearglist(p: *parser, closekind: i32, headout: **node) void = {
fn parsearglist(p: *parser, closekind: tkind, headout: **node) void = {
*headout = nil;
if (p.curkind == closekind) { return; };
let head: *node = nil;
@@ -355,7 +355,7 @@ fn parseunary(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
let k: i32 = p.curkind;
let k: tkind = p.curkind;
if (k == tkind.TK_MINUS) {
advance(p);
let n: *node = newnode(p.a, nkind.N_UN, pf, pl, pc);
@@ -398,7 +398,7 @@ fn parseunary(p: *parser) *node = {
fn parsebin(p: *parser, lhs: *node, minp: i32) *node = {
let cur: *node = lhs;
for (true) {
let op: i32 = p.curkind;
let op: tkind = p.curkind;
let pr: i32 = bprec(op);
if (pr == 0) { return cur; };
if (pr < minp) { return cur; };
@@ -425,7 +425,7 @@ fn parseexpr(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
let op: i32 = p.curkind;
let op: tkind = p.curkind;
advance(p);
let n: *node = newnode(p.a, nkind.N_ASSIGN, pf, pl, pc);
n.op = op;

View File

@@ -24,7 +24,7 @@ type parser = struct {
// nocast: while inside `[...]` we treat ':' as the slice
// separator, not the cast operator. Mirrors parse.c's flag.
nocast: i32,
curkind: i32, // holds a `tkind` value (relaxation lets it mix)
curkind: tkind,
curfile: str,
curline: i32,
curcol: i32,
@@ -53,7 +53,7 @@ export fn parserinit(p: *parser, a: *arena, l: *lex) void = {
fn advance(p: *parser) void = { refill(p); };
fn accepttok(p: *parser, k: i32) bool = {
fn accepttok(p: *parser, k: tkind) bool = {
if (p.curkind == k) { advance(p); return true; };
return false;
};
@@ -66,7 +66,7 @@ fn errmsg(p: *parser, msg: str) void = {
p.errs += 1;
};
fn expecttok(p: *parser, k: i32, what: str) bool = {
fn expecttok(p: *parser, k: tkind, what: str) bool = {
if (p.curkind == k) { advance(p); return true; };
errmsg(p, what);
return false;
@@ -313,7 +313,7 @@ fn parsetype(p: *parser) *node = {
// and the ?/! try operators are not yet wired — they'll arrive as the
// AST diff fixture grows to need them.
fn bprec(k: i32) i32 = {
fn bprec(k: tkind) i32 = {
if (k == tkind.TK_OR) { return 1; };
if (k == tkind.TK_AND) { return 2; };
if (k == tkind.TK_EQ) { return 3; };
@@ -335,7 +335,7 @@ fn bprec(k: i32) i32 = {
return 0;
};
fn isassignop(k: i32) bool = {
fn isassignop(k: tkind) bool = {
if (k == tkind.TK_ASSIGN) { return true; };
if (k == tkind.TK_PLUSEQ) { return true; };
if (k == tkind.TK_MINUSEQ) { return true; };

View File

@@ -22,7 +22,7 @@ type skind = enum i32 {
type sym = struct {
name: str,
skind: i32,
skind: skind,
type_: *tinfo,
decl: *node,
exported: i32,
@@ -98,7 +98,7 @@ export fn scopelookup(s: *scope, name: str) *sym = {
return nil;
};
export fn scopedefine(s: *scope, name: str, k: i32, t: *tinfo, decl: *node) *sym = {
export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *sym = {
if (scopelookuplocal(s, name) != nil) { return nil; };
let sy: *sym = amalloc(s.a, 80u64): *sym;
sy.name = name;

View File

@@ -74,7 +74,7 @@ type tparam = struct {
};
type tinfo = struct {
kind: i32,
kind: tykind,
size: u64,
align: u64,
sub: *tinfo, // ptr/slice/array/chan element
@@ -120,13 +120,13 @@ type tctx = struct {
// ---- constructors -----------------------------------------------------
export fn newtype(a: *arena, k: i32) *tinfo = {
export fn newtype(a: *arena, k: tykind) *tinfo = {
let t: *tinfo = amalloc(a, 96u64): *tinfo;
t.kind = k;
return t;
};
fn prim(a: *arena, k: i32, nm: str, sz: u64, al: u64) *tinfo = {
fn prim(a: *arena, k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
let t: *tinfo = newtype(a, k);
t.name = nm;
t.size = sz;
@@ -216,7 +216,7 @@ export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = {
export fn typeisint(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: i32 = t.kind;
let k: tykind = t.kind;
if (k == tykind.TY_I8) { return true; };
if (k == tykind.TY_I16) { return true; };
if (k == tykind.TY_I32) { return true; };
@@ -238,7 +238,7 @@ export fn typeisint(t: *tinfo) bool = {
export fn typeisfloat(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: i32 = t.kind;
let k: tykind = t.kind;
if (k == tykind.TY_F32) { return true; };
if (k == tykind.TY_F64) { return true; };
if (k == tykind.TY_UNTYPED_FLOAT) { return true; };
@@ -253,7 +253,7 @@ export fn typeisnum(t: *tinfo) bool = {
export fn typeisunsigned(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: i32 = t.kind;
let k: tykind = t.kind;
if (k == tykind.TY_U8) { return true; };
if (k == tykind.TY_U16) { return true; };
if (k == tykind.TY_U32) { return true; };
@@ -266,7 +266,7 @@ export fn typeisunsigned(t: *tinfo) bool = {
export fn typeisuntyped(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: i32 = t.kind;
let k: tykind = t.kind;
if (k == tykind.TY_UNTYPED_INT) { return true; };
if (k == tykind.TY_UNTYPED_FLOAT) { return true; };
if (k == tykind.TY_UNTYPED_STR) { return true; };
@@ -282,7 +282,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
if (a == nil) { return false; };
if (b == nil) { return false; };
if (a.kind != b.kind) { return false; };
let k: i32 = a.kind;
let k: tykind = a.kind;
if (k == tykind.TY_PTR) { return typeeq(a.sub, b.sub); };
if (k == tykind.TY_SLICE) { return typeeq(a.sub, b.sub); };
if (k == tykind.TY_CHAN) { return typeeq(a.sub, b.sub); };