From 37faa46eff8210365b144ccb4758dc3967b68b48 Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 16:48:18 +0330 Subject: enum: a member is defined as soon as it is declared, so later values can name it (appendix b.2.2) --- docs/SPEC.md | 4 ++-- src/compiler/builtins.zc | 8 ++++++-- src/compiler/expr.zc | 4 ++++ tests/conformance/2-types/enum_names_own_member.zc | 8 ++++++-- tests/conformance/known.txt | 1 - 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/SPEC.md b/docs/SPEC.md index f53c469..5f84acc 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -227,7 +227,7 @@ a struct cannot contain itself by value (error: a struct can't contain itself by ### 2.8 enums -2.8.1 plain enum. `enum color { RED, GREEN = 5, BLUE };` declares the global constants `RED` 0, `GREEN` 5, `BLUE` 6, also reachable as `color.RED` in expressions and case labels. a member without a value is the previous value plus one, starting at 0. the name is optional (`enum { A, B };`). values are constant expressions (3.14). a member's value cannot name an earlier member of the same enum (the members are defined when the closing brace is reached; error: expected a constant). under `typecheck on;` the enum is a distinct type (2.9). +2.8.1 plain enum. `enum color { RED, GREEN = 5, BLUE };` declares the global constants `RED` 0, `GREEN` 5, `BLUE` 6, also reachable as `color.RED` in expressions and case labels. a member without a value is the previous value plus one, starting at 0. the name is optional (`enum { A, B };`). values are constant expressions (3.14). a member is defined as soon as it is declared, so a later member's value can name it: `enum { HI = 16, LO = HI + 1 };` gives `LO` 17. under `typecheck on;` the enum is a distinct type (2.9). 2.8.2 tagged enum. when any member has a `{ fields }` block the enum is tagged: `enum shape { circle { int r; }, rect { int w; int h; }, dot };`. rules: - it needs a name; payload fields are `int`, `float` or `let` (a word), at most 8 per member; at most 63 members; members take no explicit values. @@ -1105,7 +1105,7 @@ each item gives a minimal program, the expected behavior (per this spec) and the ### b.2 implementation bugs and limitations 1. fixed: `memdebug scrub;` is accepted and poisons freed payloads with 57005 (b.1.1). -2. enum members cannot name earlier members of the same enum: `enum { HI = 16, LO = HI + 1 };` => `expected a constant` (the members are defined after the closing brace). members of an earlier enum and constants work. +2. fixed (2.8.1): enum members could not name earlier members of the same enum: `enum { HI = 16, LO = HI + 1 };` => `expected a constant` (the members are defined after the closing brace). members of an earlier enum and constants work. 3. fixed (now error: f: is defined twice): a function could be silently redefined. `int f() { return 1; } int g() { return f() + 10; } int f() { return 2; }`, then `g()` gives 11 and a later call of `f()` gives 2. expected: error `f: is defined twice` (the message exists in the compiler and is unreachable for an ordinary second definition). 4. fixed (the definition stays): a prototype after the definition hid it. `int f() { return 1; } int f();` then `f()` => `zc: line 0: f is used but never defined`. expected: the definition stays. 5. fixed (3.5.5): `?:` with one float branch and one integer branch converted wrongly. `pf(1 ? 1 : 2.5)` prints `0.000000` (the integer 1's bits read as a float); `pf(1 ? 2.5 : 1)` prints `4612811918334230528.000000` (the taken float converted again as if an integer); `pf(0 ? 2.5 : 1)` prints 1.000000 and `pf(0 ? 1 : 2.5)` 2.500000 (these two are right). expected 1.0, 2.5, 1.0, 2.5. diff --git a/src/compiler/builtins.zc b/src/compiler/builtins.zc index 214d493..6dc586a 100644 --- a/src/compiler/builtins.zc +++ b/src/compiler/builtins.zc @@ -71,8 +71,10 @@ int ptagged(word t) { // enum name { A, B = 5 } is a plain enum: each member is a constant, plain (A) // and scoped (name.A). a member with a block of fields makes it a tagged enum // (see enum_tagged): enum name { circle { int r; }, dot }. +// the first member of the enum being read, so a member's value can name the members before it; -1 outside +int enum_open = 0 - 1; int kw_enum() { - char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft; char* fname; + char* n; char* en = 0; int first = npm, cnt = 0, tagged = 0, gi, nf, v = 0, et = 0, sk, ft, rv = 0; char* fname; stmtonly(); next(); if (tk == T_ID) { en = tv; next(); } @@ -97,7 +99,9 @@ int kw_enum() { expect(s_semi); } expect(s_rb); - } else if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; } + } else { enum_open = first; if (accept(s_eq)) { pm_val[gi] = cexpr(); pm_exp[gi] = 1; } enum_open = 0 - 1; } + if (pm_exp[gi]) rv = pm_val[gi]; else if (!pm_nf[gi]) pm_val[gi] = rv; // the running value, read by cprim + rv++; cnt++; if (!accept(s_comma)) break; if (is_op(s_rb)) break; diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index 230a2d3..e449508 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -1959,6 +1959,10 @@ word cprim() { } } } + if (tk == T_ID && enum_open >= 0) { // an earlier member of the enum being read (spec 2.8.1) + mc = npm - 1; + while (mc > enum_open) { mc--; if (pm_name[mc] == tv && !pm_nf[mc]) { next(); return pm_val[mc]; } } + } die("expected a constant"); return 0; } diff --git a/tests/conformance/2-types/enum_names_own_member.zc b/tests/conformance/2-types/enum_names_own_member.zc index 26d4739..1a49106 100644 --- a/tests/conformance/2-types/enum_names_own_member.zc +++ b/tests/conformance/2-types/enum_names_own_member.zc @@ -1,4 +1,8 @@ -// expect error: expected a constant +// expect: 17/3/6/ +// spec 2.8.1: a member is defined as soon as it is declared; a later value can name it include rt; +void pn(int x) { rt.put_n(x); rt.put_c(10); } enum e { HI = 16, LO = HI + 1 }; -fn main() { return 0; } +enum f { A, B, C, D = C + 1, E = D * 2 }; +fn main() { pn(LO); pn(D); pn(E); return 0; } +// zc0: skip diff --git a/tests/conformance/known.txt b/tests/conformance/known.txt index 22a1e3d..723098a 100644 --- a/tests/conformance/known.txt +++ b/tests/conformance/known.txt @@ -2,7 +2,6 @@ # class a: self-hosted compiler bug; b: zc0 deviation (appendix a/b); c: spec issue to settle # appendix b.2.8 (a global named in an initializer is its address) is documented behavior per 5.3.2 and 3.14.2: no entry 4-statements/o3_outside_function_ordinary_block.zc zy c top-level o3 { } is rejected; spec 4.10.2 says it is an ordinary block -5-declarations/enum_member_names_earlier_b2.zc zy c appendix b.2.2 confirmed, but spec 2.8.1 documents it (members are defined at the closing brace); not fixed, like b.2.8 the spec body wins until decided 5-declarations/fn_prototype_unnamed_params_b13.zc zy c spec 5.2.1 says prototype parameter names are required, and a type name alone is read as an untyped parameter of that name (paramstart); b.2.13 is a spec choice, not fixed 9-appendix-b/b2_06_bool_normalises.zc zy c appendix b.2.6 confirmed, but spec 2.1 documents bool as a number that is not normalised 9-appendix-b/b2_10_recovery_hides_errors.zc zy c appendix b.2.10 confirmed; by design per 8.2 -- cgit v1.2.3