wcc: Hare-style enum [storage] { ... } type
`type Foo = enum [intT] { NAME [= expr], ... };`. Storage defaults
to i32; members auto-increment from 0 (or last+1) when `= expr` is
omitted, and value expressions can reference earlier siblings —
enough surface for io::mode-style flag enums (`RDWR = READ | WRITE`).
`Foo.MEMBER` folds to an N_INTLIT in the checker, typed as the
named enum. Binops on enum values yield the same enum (type_eq on
the named pointer), so `mode.R | mode.W` is a `mode`. Enum ↔ int
is a reinterpret-only `as` cast — same register, no tag wrap — so
`mode.RDWR as i32` and `1 as mode` both work without runtime ops.
`is`/`?`/`!` are still tagged-union-only. CSP runtime (chan/proc)
is unchanged; only the type-system slot is touched here.
This commit is contained in:
163
cmd/wcc/check.c
163
cmd/wcc/check.c
@@ -164,6 +164,85 @@ tagged_success_type(Type *u)
|
||||
return u->params ? u->params->type : NULL;
|
||||
}
|
||||
|
||||
/* eval_enum_value — fold an enum member-value expression to a u64
|
||||
* constant. Sees prior siblings via the `prev` Tfield list (each
|
||||
* carries the member's name and resolved value in .offset). Returns
|
||||
* 1 on success; on failure emits the error and returns 0. The op set
|
||||
* is the constant subset typical of Hare-style flag enums:
|
||||
* literal, sibling-ident, + - * / % & | ^ << >>, unary - and ~. */
|
||||
static int
|
||||
eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out)
|
||||
{
|
||||
if (n == NULL) return 0;
|
||||
switch (n->kind) {
|
||||
case N_INTLIT:
|
||||
case N_RUNELIT:
|
||||
*out = n->uval;
|
||||
return 1;
|
||||
case N_TRUE: *out = 1; return 1;
|
||||
case N_FALSE: *out = 0; return 1;
|
||||
case N_IDENT: {
|
||||
for (Tfield *f = prev; f; f = f->next) {
|
||||
if (f->name && n->str &&
|
||||
strcmp(f->name, n->str) == 0) {
|
||||
*out = f->offset;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
err(c, n->pos, "enum value: unknown identifier '%s'",
|
||||
n->str ? n->str : "?");
|
||||
return 0;
|
||||
}
|
||||
case N_BIN: {
|
||||
u64 a, b;
|
||||
if (!eval_enum_value(c, n->lhs, prev, &a) ||
|
||||
!eval_enum_value(c, n->rhs, prev, &b))
|
||||
return 0;
|
||||
switch (n->op) {
|
||||
case TK_PLUS: *out = a + b; return 1;
|
||||
case TK_MINUS: *out = a - b; return 1;
|
||||
case TK_STAR: *out = a * b; return 1;
|
||||
case TK_SLASH:
|
||||
if (b == 0) goto divzero;
|
||||
*out = a / b; return 1;
|
||||
case TK_PERCENT:
|
||||
if (b == 0) goto divzero;
|
||||
*out = a % b; return 1;
|
||||
case TK_AMP: *out = a & b; return 1;
|
||||
case TK_PIPE: *out = a | b; return 1;
|
||||
case TK_CARET: *out = a ^ b; return 1;
|
||||
case TK_LSHIFT: *out = a << b; return 1;
|
||||
case TK_RSHIFT: *out = a >> b; return 1;
|
||||
default:
|
||||
err(c, n->pos, "enum value: unsupported binary op %s",
|
||||
tokname(n->op));
|
||||
return 0;
|
||||
}
|
||||
divzero:
|
||||
err(c, n->pos, "enum value: division by zero");
|
||||
return 0;
|
||||
}
|
||||
case N_UN: {
|
||||
u64 v;
|
||||
if (!eval_enum_value(c, n->lhs, prev, &v))
|
||||
return 0;
|
||||
switch (n->op) {
|
||||
case TK_MINUS: *out = (u64)(-(i64)v); return 1;
|
||||
case TK_TILDE: *out = ~v; return 1;
|
||||
case TK_PLUS: *out = v; return 1;
|
||||
default:
|
||||
err(c, n->pos, "enum value: unsupported unary op %s",
|
||||
tokname(n->op));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
default:
|
||||
err(c, n->pos,
|
||||
"enum value must be a constant integer expression");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static Type *
|
||||
resolve_type(Checker *c, Node *n)
|
||||
{
|
||||
@@ -387,6 +466,50 @@ resolve_type(Checker *c, Node *n)
|
||||
t->size = (off + maxalign - 1) & ~(maxalign - 1);
|
||||
return t;
|
||||
}
|
||||
case N_TENUM: {
|
||||
Type *t = newtype(c->a, TY_ENUM);
|
||||
Type *storage = ty_i32; /* default storage */
|
||||
if (n->lhs) {
|
||||
Type *s = resolve_type(c, n->lhs);
|
||||
if (s == ty_err || !type_isint(s))
|
||||
err(c, n->lhs->pos,
|
||||
"enum storage type must be integer");
|
||||
else
|
||||
storage = s;
|
||||
}
|
||||
t->sub = storage;
|
||||
t->size = storage->size;
|
||||
t->align = storage->align;
|
||||
Tfield *head = NULL, *tail = NULL;
|
||||
u64 prev = (u64)-1; /* so first omitted → 0 */
|
||||
for (Node *m = n->list; m; m = m->next) {
|
||||
u64 val;
|
||||
if (m->lhs == NULL) {
|
||||
val = prev + 1;
|
||||
} else if (!eval_enum_value(c, m->lhs, head, &val)) {
|
||||
val = prev + 1;
|
||||
}
|
||||
prev = val;
|
||||
for (Tfield *e = head; e; e = e->next) {
|
||||
if (e->name && m->str &&
|
||||
strcmp(e->name, m->str) == 0) {
|
||||
err(c, m->pos,
|
||||
"duplicate enum member '%s'",
|
||||
m->str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Tfield *tf = amalloc(c->a, sizeof *tf);
|
||||
tf->name = m->str;
|
||||
tf->type = NULL;
|
||||
tf->offset = val;
|
||||
if (head == NULL) head = tf;
|
||||
else tail->next = tf;
|
||||
tail = tf;
|
||||
}
|
||||
t->fields = head;
|
||||
return t;
|
||||
}
|
||||
default:
|
||||
return err(c, n->pos, "expected type expression");
|
||||
}
|
||||
@@ -555,6 +678,34 @@ cexpr(Checker *c, Node *n)
|
||||
* missing. */
|
||||
return n->type = ty_err;
|
||||
}
|
||||
/* enum member access: TypeName.MEMBER → fold to
|
||||
* the member's integer literal value. Type is the
|
||||
* (named) enum type itself, so bitwise ops between
|
||||
* members yield the same enum type via type_eq. */
|
||||
if (ms && ms->kind == SK_TYPE && ms->type) {
|
||||
Type *u = (ms->type->kind == TY_NAMED)
|
||||
? ms->type->under : ms->type;
|
||||
if (u && u->kind == TY_ENUM) {
|
||||
for (Tfield *f = u->fields; f; f = f->next) {
|
||||
if (f->name && n->str &&
|
||||
strcmp(f->name, n->str) == 0) {
|
||||
n->kind = N_INTLIT;
|
||||
n->uval = f->offset;
|
||||
n->str = aprintf(c->a, "%llu",
|
||||
(unsigned long long)f->offset);
|
||||
n->strlen = strlen(n->str);
|
||||
n->lhs = NULL;
|
||||
n->rhs = NULL;
|
||||
n->tsuffix = NULL;
|
||||
return n->type = ms->type;
|
||||
}
|
||||
}
|
||||
return n->type = err(c, n->pos,
|
||||
"no enum member '%s' in %s",
|
||||
n->str ? n->str : "?",
|
||||
ms->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Type *base = cexpr(c, n->lhs);
|
||||
if (base == NULL || base == ty_err) return n->type = ty_err;
|
||||
@@ -991,6 +1142,18 @@ cexpr(Checker *c, Node *n)
|
||||
* itself (as). */
|
||||
if (n->rhs) n->rhs->type = vt;
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
/* Enum ↔ integer cast: `enumval as intT` or `int as enumT`.
|
||||
* Reinterpret-only — the storage shape is already integer, so
|
||||
* cgen treats the cast as a no-op (the value lives in the same
|
||||
* register). The `is` form is rejected; enums aren't sums. */
|
||||
Type *uu = u;
|
||||
Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt;
|
||||
int lhs_enum = uu && uu->kind == TY_ENUM;
|
||||
int rhs_enum = vu && vu->kind == TY_ENUM;
|
||||
if (n->kind == N_TYPEASSERT && (lhs_enum || rhs_enum) &&
|
||||
type_isint(t) && type_isint(vt)) {
|
||||
return n->type = vt;
|
||||
}
|
||||
if (u == NULL || u->kind != TY_TAGGED) {
|
||||
const char *op = (n->kind == N_TYPETEST) ? "is" : "as";
|
||||
return n->type = err(c, n->pos,
|
||||
|
||||
Reference in New Issue
Block a user