check: reject invalid enum decls in wwstage (catB-2)
The wwstage checker silently accepted enums with a non-integer storage type, duplicate members, or a non-constant member value; cstage already rejects all three (cmd/wcc/check.c:1000-1042). Add validateenummembers, a pure read-only diagnostic dispatched once per enum decl from resolvewalk's N_TENUM arm (check.ww:791, beside stampenumvals -- not the per-query recompute arms), mirroring the catB-7/14 validatestructfields pattern. Storage gate uses typeisint on the resolved tinfo (the exact type_isint mirror: chases TY_NAMED.under and TY_ENUM.sub, so an int-alias storage is accepted; raw-AST isinttypeast would not). Duplicate members: O(n^2) name walk. Unfoldable values reuse enumvalfold with until=member (forward-only). Emits via cerr + c.errs, no mutation, so valid-program codegen is unchanged and cstage==wwstage byte-id holds. wwstage's value-fold message is intentionally generic where cstage's eval_enum_value gives per-reason text (enumvalfold returns a bool, not a reason); both reject. Documented at the site, filed as follow-up task #10. Test: new table-driven both-stage reject test 850_enum_reject (non-int storage / duplicate member / forward-ref rows + a distinct-member control whose backward-ref value builds and runs). Full make test: 452 green incl. 990-997 byte-id.
This commit is contained in:
@@ -788,7 +788,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
if (k == syntax.nkind.N_TENUM) { stampenumvals(c, n); };
|
||||
if (k == syntax.nkind.N_TENUM) { stampenumvals(c, n); validateenummembers(c, n); };
|
||||
if (k == syntax.nkind.N_TSTRUCT) { validatestructfields(c, n); };
|
||||
|
||||
// #42's size/align/offset fold trigger lived here pre-A.6.0; the
|
||||
@@ -1894,6 +1894,72 @@ fn validatestructfields(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// validateenummembers — reject the three invalid enum-decl shapes
|
||||
// cstage rejects at cmd/wcc/check.c:1000-1042 (the N_TENUM arm of
|
||||
// resolve_type): a non-integer storage type, a duplicate member name,
|
||||
// and an unfoldable (non-constant) member value-expr. Pure diagnostic:
|
||||
// it mutates no n.type_ / member value / checker state beyond bumping
|
||||
// c.errs, so valid programs (integer storage, distinct members,
|
||||
// foldable values) emit nothing and their stamping/codegen is untouched
|
||||
// — the byte-id safety condition. Fires once per enum decl from
|
||||
// resolvewalk's eager type-decl dispatch, sibling to validatestructfields
|
||||
// (rule-10 symmetric with cstage's once-per-resolve_type), not from the
|
||||
// per-query size/align arms.
|
||||
fn validateenummembers(c: *checker, n: *syntax.node) void = {
|
||||
// storage type: cstage resolves n->lhs then gates type_isint
|
||||
// (check.c:1004-1009); default storage is i32, always integer.
|
||||
if (n.lhs != nil) {
|
||||
let s: *syntax.tinfo = tinfofornode(c, n.lhs);
|
||||
if (!syntax.typeisint(s)) {
|
||||
cerr(n.lhs.file);
|
||||
cerr(": error: enum storage type must be integer\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let m: *syntax.node = n.list;
|
||||
for (m != nil) {
|
||||
if (m.kind == syntax.nkind.N_TENUMMEMBER && m.str.len != 0) {
|
||||
// duplicate member: cstage compares each member against
|
||||
// the earlier ones (check.c:1024-1032).
|
||||
let e: *syntax.node = n.list;
|
||||
for (e != m) {
|
||||
if (e.kind == syntax.nkind.N_TENUMMEMBER
|
||||
&& e.str.len != 0
|
||||
&& syntax.streq(e.str, m.str)) {
|
||||
cerr(m.file);
|
||||
cerr(": error: duplicate enum member '");
|
||||
cerr(m.str);
|
||||
cerr("'\n");
|
||||
c.errs += 1;
|
||||
break;
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// unfoldable value: cstage delegates to eval_enum_value
|
||||
// (check.c:1020), which emits a SHAPE-SPECIFIC reason —
|
||||
// "enum value: unknown identifier 'B'" for a forward sibling
|
||||
// ref (check.c:316), "...division by zero" (327), "...unsupported
|
||||
// binary/unary op" (330/343), else the generic constant-expr
|
||||
// message (349). enumvalfold returns a bare bool (no reason), so
|
||||
// wwstage collapses all of these to the generic "enum value must
|
||||
// be a constant integer expression". Both stages REJECT (errs
|
||||
// counted, build gated) → functionally symmetric, no asm impact;
|
||||
// the message-specificity gap is the lone divergence, retained
|
||||
// and tracked as task #10 (rule-7: documented, not silent).
|
||||
// `until = m` enforces harec's forward-only sibling-ref discipline.
|
||||
if (m.lhs != nil) {
|
||||
let v: u64 = 0u64;
|
||||
if (!enumvalfold(n, m, m.lhs, &v)) {
|
||||
cerr(m.file);
|
||||
cerr(": error: enum value must be a constant integer expression\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
m = m.next;
|
||||
};
|
||||
};
|
||||
|
||||
// stampnilexpr — stamp nil-typed nodes in a constant expr subtree to
|
||||
// `ti`. lhs/rhs cover the enum constexpr grammar enumvalfold accepts
|
||||
// (literals, unary, binary, sibling backref); non-nil nodes keep the
|
||||
|
||||
Reference in New Issue
Block a user