selfhost: cgen for enum (member fold + as pass-through)

w6c_ww now compiles enum end-to-end and emits byte-identical
asm to the C w6c on the new 994 corpus case (`type mode = enum u8
{ R, W, RW = R | W }; main() { return (mode.RW): i32 }`). Mechanism
mirrors the C side:

- collectenums walks every `type X = enum {...}` at file scope and
  pre-resolves each member's u64 value (auto-increment from prior,
  sibling-ref folding for `RDWR = READ | WRITE`).
- cgdot recognises `EnumName.MEMBER` before the local lookup and
  emits MOVQ $value, AX directly.
- cgtypeassert short-circuits when either side is enum: cgexpr on
  the LHS lands the value in AX with the right integer width; no
  tag/unwrap.

main.combined.ww (wwdump/ + w6c/) regenerated by ww build.
This commit is contained in:
2026-05-12 04:25:28 +09:00
parent 9227a07f91
commit f597ce67f6
6 changed files with 642 additions and 0 deletions

View File

@@ -5943,7 +5943,57 @@ fn cgtypetest(c: *cgen, n: *node) void = {
return;
};
// isenumexpr — does this expression's static type resolve to an enum?
// Recognises enum-member access (`Foo.MEMBER`), enum-typed local
// idents, and N_BIN whose either operand is enum (so `R | W` flows
// through the cast pass-through too).
fn isenumexpr(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_DOT) {
if (e.lhs != nil) {
if (e.lhs.kind == N_IDENT) {
if (enumlookup(c, e.lhs.str) != nil) { return true; };
};
};
};
if (k == N_IDENT) {
let lc: *local = localfindnode(c, e.str);
if (lc != nil) {
if (lc.tnode != nil) {
if (lc.tnode.kind == N_TNAME) {
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
};
};
};
};
if (k == N_BIN) {
if (isenumexpr(c, e.lhs)) { return true; };
if (isenumexpr(c, e.rhs)) { return true; };
};
if (k == N_UN) {
if (isenumexpr(c, e.lhs)) { return true; };
};
return false;
};
fn isenumtype(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == N_TENUM) { return true; };
if (t.kind == N_TNAME) {
if (enumlookup(c, t.str) != nil) { return true; };
};
return false;
};
fn cgtypeassert(c: *cgen, n: *node) void = {
// Enum ↔ integer: reinterpret-only. The LHS value already
// occupies AX (or AX:BX for str variants, irrelevant here);
// no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit.
if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) {
cgexpr(c, n.lhs);
return;
};
// `e as T` — load tag, abort (exit 1) if tag != T's variant
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
fn cgdot(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
let fld: str = n.str;
// Enum member access: `EnumName.MEMBER` → inline the constant.
// Pre-computed at collectenums time; no value-expr fold here.
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let en: *enumtype = enumlookup(c, lhs.str);
if (en != nil) {
let v: u64;
if (enummemberval(en, fld, &v)) {
emitline("\tMOVQ\t$");
emitint(v: i64);
emitline(", AX\n");
return;
};
};
};
};
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let nm: str = lhs.str;
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
c.strlitseq = 0;
collectaliases(c, file);
collectstructs(c, file);
collectenums(c, file);
collectdefs(c, file);
collectfnrets(c, file);
fficollect(c, file);
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
return nil;
};
// ---- enum registry --------------------------------------------------
//
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
// every `type Foo = enum [storage] { ... }`, pre-compute each
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_INTLIT) { *out = e.uval; return true; };
if (k == N_RUNELIT) { *out = e.uval; return true; };
if (k == N_TRUE) { *out = 1u64; return true; };
if (k == N_FALSE) { *out = 0u64; return true; };
if (k == N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
if (streq(m.mname, e.str)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
if (k == N_BIN) {
let a: u64;
let b: u64;
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
let op: i32 = e.op;
if (op == TK_PLUS) { *out = a + b; return true; };
if (op == TK_MINUS) { *out = a - b; return true; };
if (op == TK_STAR) { *out = a * b; return true; };
if (op == TK_SLASH) {
if (b == 0u64) { return false; };
*out = a / b; return true;
};
if (op == TK_PERCENT) {
if (b == 0u64) { return false; };
*out = a % b; return true;
};
if (op == TK_AMP) { *out = a & b; return true; };
if (op == TK_PIPE) { *out = a | b; return true; };
if (op == TK_CARET) { *out = a ^ b; return true; };
if (op == TK_LSHIFT) { *out = a << b; return true; };
if (op == TK_RSHIFT) { *out = a >> b; return true; };
return false;
};
if (k == N_UN) {
let v: u64;
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
let op: i32 = e.op;
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == TK_TILDE) { *out = ~v; return true; };
if (op == TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn collectenums(c: *cgen, file: *node) void = {
c.enums = nil;
let d: *node = file.list;
for (d != nil) {
if (d.kind == N_TYPEDECL) {
let body: *node = d.lhs;
if (body != nil) {
if (body.kind == N_TENUM) {
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
et.ename = d.str;
et.storage = body.lhs;
et.members = nil;
let prev: u64 = (-1i64): u64;
let mhead: *enummember = nil;
let mtail: *enummember = nil;
let m: *node = body.list;
for (m != nil) {
let val: u64;
if (m.lhs == nil) {
val = prev + 1u64;
} else {
if (!enumevalmember(mhead, m.lhs, &val)) {
val = prev + 1u64;
};
};
prev = val;
let em: *enummember = amalloc(c.a, 32u64): *enummember;
em.mname = m.str;
em.mval = val;
em.emnext = nil;
if (mhead == nil) { mhead = em; mtail = em; }
else { mtail.emnext = em; mtail = em; };
m = m.next;
};
et.members = mhead;
et.etnext = c.enums;
c.enums = et;
};
};
};
d = d.next;
};
};
fn enumlookup(c: *cgen, name: str) *enumtype = {
let e: *enumtype = c.enums;
for (e != nil) {
if (streq(e.ename, name)) { return e; };
e = e.etnext;
};
return nil;
};
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
let m: *enummember = en.members;
for (m != nil) {
if (streq(m.mname, mname)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
// resolvetype — follow typedef alias chains to a "canonical" type
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
fn resolvetype(c: *cgen, t: *node) *node = {
@@ -8367,6 +8560,24 @@ type ffi = struct {
fnext: *ffi,
};
// enummember — one (name, value) pair belonging to a registered enum.
// Values are pre-computed at collect time (Hare allows sibling refs
// like `RDWR = READ | WRITE`, so we walk the value expr against the
// already-resolved siblings). Lookup is linear; enum cardinality is
// usually small.
type enummember = struct {
mname: str,
mval: u64,
emnext: *enummember,
};
type enumtype = struct {
ename: str,
storage: *node, // AST type expr for the storage type (i32 by default)
members: *enummember,
etnext: *enumtype,
};
def LOOP_MAX: i32 = 16;
def DEFER_MAX: i32 = 16;
@@ -8383,6 +8594,7 @@ type cgen = struct {
fnrets: *fnret,
aliases: *aliasent,
structs: *structinfo,
enums: *enumtype,
mods: *modent, // non-exported decls → originating module
fnname: str,
fnret: *node, // declared return type of current fn (or nil)

View File

@@ -80,6 +80,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
return nil;
};
// ---- enum registry --------------------------------------------------
//
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
// every `type Foo = enum [storage] { ... }`, pre-compute each
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_INTLIT) { *out = e.uval; return true; };
if (k == N_RUNELIT) { *out = e.uval; return true; };
if (k == N_TRUE) { *out = 1u64; return true; };
if (k == N_FALSE) { *out = 0u64; return true; };
if (k == N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
if (streq(m.mname, e.str)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
if (k == N_BIN) {
let a: u64;
let b: u64;
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
let op: i32 = e.op;
if (op == TK_PLUS) { *out = a + b; return true; };
if (op == TK_MINUS) { *out = a - b; return true; };
if (op == TK_STAR) { *out = a * b; return true; };
if (op == TK_SLASH) {
if (b == 0u64) { return false; };
*out = a / b; return true;
};
if (op == TK_PERCENT) {
if (b == 0u64) { return false; };
*out = a % b; return true;
};
if (op == TK_AMP) { *out = a & b; return true; };
if (op == TK_PIPE) { *out = a | b; return true; };
if (op == TK_CARET) { *out = a ^ b; return true; };
if (op == TK_LSHIFT) { *out = a << b; return true; };
if (op == TK_RSHIFT) { *out = a >> b; return true; };
return false;
};
if (k == N_UN) {
let v: u64;
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
let op: i32 = e.op;
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == TK_TILDE) { *out = ~v; return true; };
if (op == TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn collectenums(c: *cgen, file: *node) void = {
c.enums = nil;
let d: *node = file.list;
for (d != nil) {
if (d.kind == N_TYPEDECL) {
let body: *node = d.lhs;
if (body != nil) {
if (body.kind == N_TENUM) {
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
et.ename = d.str;
et.storage = body.lhs;
et.members = nil;
let prev: u64 = (-1i64): u64;
let mhead: *enummember = nil;
let mtail: *enummember = nil;
let m: *node = body.list;
for (m != nil) {
let val: u64;
if (m.lhs == nil) {
val = prev + 1u64;
} else {
if (!enumevalmember(mhead, m.lhs, &val)) {
val = prev + 1u64;
};
};
prev = val;
let em: *enummember = amalloc(c.a, 32u64): *enummember;
em.mname = m.str;
em.mval = val;
em.emnext = nil;
if (mhead == nil) { mhead = em; mtail = em; }
else { mtail.emnext = em; mtail = em; };
m = m.next;
};
et.members = mhead;
et.etnext = c.enums;
c.enums = et;
};
};
};
d = d.next;
};
};
fn enumlookup(c: *cgen, name: str) *enumtype = {
let e: *enumtype = c.enums;
for (e != nil) {
if (streq(e.ename, name)) { return e; };
e = e.etnext;
};
return nil;
};
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
let m: *enummember = en.members;
for (m != nil) {
if (streq(m.mname, mname)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
// resolvetype — follow typedef alias chains to a "canonical" type
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
fn resolvetype(c: *cgen, t: *node) *node = {
@@ -144,6 +270,24 @@ type ffi = struct {
fnext: *ffi,
};
// enummember — one (name, value) pair belonging to a registered enum.
// Values are pre-computed at collect time (Hare allows sibling refs
// like `RDWR = READ | WRITE`, so we walk the value expr against the
// already-resolved siblings). Lookup is linear; enum cardinality is
// usually small.
type enummember = struct {
mname: str,
mval: u64,
emnext: *enummember,
};
type enumtype = struct {
ename: str,
storage: *node, // AST type expr for the storage type (i32 by default)
members: *enummember,
etnext: *enumtype,
};
def LOOP_MAX: i32 = 16;
def DEFER_MAX: i32 = 16;
@@ -160,6 +304,7 @@ type cgen = struct {
fnrets: *fnret,
aliases: *aliasent,
structs: *structinfo,
enums: *enumtype,
mods: *modent, // non-exported decls → originating module
fnname: str,
fnret: *node, // declared return type of current fn (or nil)

View File

@@ -279,6 +279,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
c.strlitseq = 0;
collectaliases(c, file);
collectstructs(c, file);
collectenums(c, file);
collectdefs(c, file);
collectfnrets(c, file);
fficollect(c, file);

View File

@@ -251,7 +251,57 @@ fn cgtypetest(c: *cgen, n: *node) void = {
return;
};
// isenumexpr — does this expression's static type resolve to an enum?
// Recognises enum-member access (`Foo.MEMBER`), enum-typed local
// idents, and N_BIN whose either operand is enum (so `R | W` flows
// through the cast pass-through too).
fn isenumexpr(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_DOT) {
if (e.lhs != nil) {
if (e.lhs.kind == N_IDENT) {
if (enumlookup(c, e.lhs.str) != nil) { return true; };
};
};
};
if (k == N_IDENT) {
let lc: *local = localfindnode(c, e.str);
if (lc != nil) {
if (lc.tnode != nil) {
if (lc.tnode.kind == N_TNAME) {
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
};
};
};
};
if (k == N_BIN) {
if (isenumexpr(c, e.lhs)) { return true; };
if (isenumexpr(c, e.rhs)) { return true; };
};
if (k == N_UN) {
if (isenumexpr(c, e.lhs)) { return true; };
};
return false;
};
fn isenumtype(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == N_TENUM) { return true; };
if (t.kind == N_TNAME) {
if (enumlookup(c, t.str) != nil) { return true; };
};
return false;
};
fn cgtypeassert(c: *cgen, n: *node) void = {
// Enum ↔ integer: reinterpret-only. The LHS value already
// occupies AX (or AX:BX for str variants, irrelevant here);
// no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit.
if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) {
cgexpr(c, n.lhs);
return;
};
// `e as T` — load tag, abort (exit 1) if tag != T's variant
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
@@ -565,6 +615,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
fn cgdot(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
let fld: str = n.str;
// Enum member access: `EnumName.MEMBER` → inline the constant.
// Pre-computed at collectenums time; no value-expr fold here.
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let en: *enumtype = enumlookup(c, lhs.str);
if (en != nil) {
let v: u64;
if (enummemberval(en, fld, &v)) {
emitline("\tMOVQ\t$");
emitint(v: i64);
emitline(", AX\n");
return;
};
};
};
};
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let nm: str = lhs.str;

View File

@@ -5943,7 +5943,57 @@ fn cgtypetest(c: *cgen, n: *node) void = {
return;
};
// isenumexpr — does this expression's static type resolve to an enum?
// Recognises enum-member access (`Foo.MEMBER`), enum-typed local
// idents, and N_BIN whose either operand is enum (so `R | W` flows
// through the cast pass-through too).
fn isenumexpr(c: *cgen, e: *node) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_DOT) {
if (e.lhs != nil) {
if (e.lhs.kind == N_IDENT) {
if (enumlookup(c, e.lhs.str) != nil) { return true; };
};
};
};
if (k == N_IDENT) {
let lc: *local = localfindnode(c, e.str);
if (lc != nil) {
if (lc.tnode != nil) {
if (lc.tnode.kind == N_TNAME) {
if (enumlookup(c, lc.tnode.str) != nil) { return true; };
};
};
};
};
if (k == N_BIN) {
if (isenumexpr(c, e.lhs)) { return true; };
if (isenumexpr(c, e.rhs)) { return true; };
};
if (k == N_UN) {
if (isenumexpr(c, e.lhs)) { return true; };
};
return false;
};
fn isenumtype(c: *cgen, t: *node) bool = {
if (t == nil) { return false; };
if (t.kind == N_TENUM) { return true; };
if (t.kind == N_TNAME) {
if (enumlookup(c, t.str) != nil) { return true; };
};
return false;
};
fn cgtypeassert(c: *cgen, n: *node) void = {
// Enum ↔ integer: reinterpret-only. The LHS value already
// occupies AX (or AX:BX for str variants, irrelevant here);
// no tag/unwrap. Matches cmd/w6c/cgen.c's same short-circuit.
if (isenumexpr(c, n.lhs) || isenumtype(c, n.rhs)) {
cgexpr(c, n.lhs);
return;
};
// `e as T` — load tag, abort (exit 1) if tag != T's variant
// index, otherwise unwrap to T's ABI: scalar/ptr → AX, 16B
// str → (AX, BX). Mirrors cgmatch's slot-based value load.
@@ -6257,6 +6307,22 @@ fn cgmatch(c: *cgen, n: *node) void = {
fn cgdot(c: *cgen, n: *node) void = {
let lhs: *node = n.lhs;
let fld: str = n.str;
// Enum member access: `EnumName.MEMBER` → inline the constant.
// Pre-computed at collectenums time; no value-expr fold here.
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let en: *enumtype = enumlookup(c, lhs.str);
if (en != nil) {
let v: u64;
if (enummemberval(en, fld, &v)) {
emitline("\tMOVQ\t$");
emitint(v: i64);
emitline(", AX\n");
return;
};
};
};
};
if (lhs != nil) {
if (lhs.kind == N_IDENT) {
let nm: str = lhs.str;
@@ -8203,6 +8269,7 @@ export fn cgfile(c: *cgen, file: *node) void = {
c.strlitseq = 0;
collectaliases(c, file);
collectstructs(c, file);
collectenums(c, file);
collectdefs(c, file);
collectfnrets(c, file);
fficollect(c, file);
@@ -8303,6 +8370,132 @@ fn aliaslookup(c: *cgen, name: str) *node = {
return nil;
};
// ---- enum registry --------------------------------------------------
//
// Mirrors cmd/wcc/check.c's enum resolution at collect time: walk
// every `type Foo = enum [storage] { ... }`, pre-compute each
// member's u64 value (supporting auto-increment and sibling refs),
// and stash them so cgdot can fold `Foo.MEMBER` → MOVQ $value, AX.
fn enumevalmember(prev: *enummember, e: *node, out: *u64) bool = {
if (e == nil) { return false; };
let k: i32 = e.kind;
if (k == N_INTLIT) { *out = e.uval; return true; };
if (k == N_RUNELIT) { *out = e.uval; return true; };
if (k == N_TRUE) { *out = 1u64; return true; };
if (k == N_FALSE) { *out = 0u64; return true; };
if (k == N_IDENT) {
let m: *enummember = prev;
for (m != nil) {
if (streq(m.mname, e.str)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
if (k == N_BIN) {
let a: u64;
let b: u64;
if (!enumevalmember(prev, e.lhs, &a)) { return false; };
if (!enumevalmember(prev, e.rhs, &b)) { return false; };
let op: i32 = e.op;
if (op == TK_PLUS) { *out = a + b; return true; };
if (op == TK_MINUS) { *out = a - b; return true; };
if (op == TK_STAR) { *out = a * b; return true; };
if (op == TK_SLASH) {
if (b == 0u64) { return false; };
*out = a / b; return true;
};
if (op == TK_PERCENT) {
if (b == 0u64) { return false; };
*out = a % b; return true;
};
if (op == TK_AMP) { *out = a & b; return true; };
if (op == TK_PIPE) { *out = a | b; return true; };
if (op == TK_CARET) { *out = a ^ b; return true; };
if (op == TK_LSHIFT) { *out = a << b; return true; };
if (op == TK_RSHIFT) { *out = a >> b; return true; };
return false;
};
if (k == N_UN) {
let v: u64;
if (!enumevalmember(prev, e.lhs, &v)) { return false; };
let op: i32 = e.op;
if (op == TK_MINUS) { *out = (-(v: i64)): u64; return true; };
if (op == TK_TILDE) { *out = ~v; return true; };
if (op == TK_PLUS) { *out = v; return true; };
return false;
};
return false;
};
fn collectenums(c: *cgen, file: *node) void = {
c.enums = nil;
let d: *node = file.list;
for (d != nil) {
if (d.kind == N_TYPEDECL) {
let body: *node = d.lhs;
if (body != nil) {
if (body.kind == N_TENUM) {
let et: *enumtype = amalloc(c.a, 48u64): *enumtype;
et.ename = d.str;
et.storage = body.lhs;
et.members = nil;
let prev: u64 = (-1i64): u64;
let mhead: *enummember = nil;
let mtail: *enummember = nil;
let m: *node = body.list;
for (m != nil) {
let val: u64;
if (m.lhs == nil) {
val = prev + 1u64;
} else {
if (!enumevalmember(mhead, m.lhs, &val)) {
val = prev + 1u64;
};
};
prev = val;
let em: *enummember = amalloc(c.a, 32u64): *enummember;
em.mname = m.str;
em.mval = val;
em.emnext = nil;
if (mhead == nil) { mhead = em; mtail = em; }
else { mtail.emnext = em; mtail = em; };
m = m.next;
};
et.members = mhead;
et.etnext = c.enums;
c.enums = et;
};
};
};
d = d.next;
};
};
fn enumlookup(c: *cgen, name: str) *enumtype = {
let e: *enumtype = c.enums;
for (e != nil) {
if (streq(e.ename, name)) { return e; };
e = e.etnext;
};
return nil;
};
fn enummemberval(en: *enumtype, mname: str, out: *u64) bool = {
let m: *enummember = en.members;
for (m != nil) {
if (streq(m.mname, mname)) {
*out = m.mval;
return true;
};
m = m.emnext;
};
return false;
};
// resolvetype — follow typedef alias chains to a "canonical" type
// expr (str/slice/array/struct/...). Stops on cycles via depth limit.
fn resolvetype(c: *cgen, t: *node) *node = {
@@ -8367,6 +8560,24 @@ type ffi = struct {
fnext: *ffi,
};
// enummember — one (name, value) pair belonging to a registered enum.
// Values are pre-computed at collect time (Hare allows sibling refs
// like `RDWR = READ | WRITE`, so we walk the value expr against the
// already-resolved siblings). Lookup is linear; enum cardinality is
// usually small.
type enummember = struct {
mname: str,
mval: u64,
emnext: *enummember,
};
type enumtype = struct {
ename: str,
storage: *node, // AST type expr for the storage type (i32 by default)
members: *enummember,
etnext: *enumtype,
};
def LOOP_MAX: i32 = 16;
def DEFER_MAX: i32 = 16;
@@ -8383,6 +8594,7 @@ type cgen = struct {
fnrets: *fnret,
aliases: *aliasent,
structs: *structinfo,
enums: *enumtype,
mods: *modent, // non-exported decls → originating module
fnname: str,
fnret: *node, // declared return type of current fn (or nil)

View File

@@ -143,6 +143,12 @@ main(void)
" return n * fact(n - 1);\n"
"};\n"
"fn main() i32 = { return fact(5); };" },
{ "enum",
"type mode = enum u8 { R = 1, W = 2, RW = R | W };\n"
"fn main() i32 = {\n"
" let m: mode = mode.RW;\n"
" return m as i32;\n"
"};" },
{ NULL, NULL },
};