diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index 5bd8554a..7f48f257 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -275,6 +275,15 @@ type_eq(Type *a, Type *b) } case TY_NAMED: return a == b; /* nominally equal only when same node */ + case TY_ENUM: + /* Enums are nominal (harec: an enum IS its alias type): + * distinct decls are distinct types even with identical + * storage/members. Falling into the primitive default made + * ANY two enums equal — the wwstage variant matcher picked + * the first enum variant of a union for a value of the + * OTHER enum (live cs!=ww, wrong tag at runtime). Same-decl + * chased ends hit the a == b fast path above. */ + return 0; case TY_TUPLE: { Tparam *pa = a->params, *pb = b->params; while (pa && pb) { diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 643e2845..cb37c108 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1751; +def corpuscount: i32 = 1752; def errorcount: i32 = 349; def compilecount: i32 = 21; def runcount: i32 = 209; -def runexitcount: i32 = 1172; -def nativecount: i32 = 3502; -def corpushash: str = "0d0401f0d80ca682b0872343a62db4f91c0ff4c68e71f97cd5358cf92faffebc"; +def runexitcount: i32 = 1173; +def nativecount: i32 = 3504; +def corpushash: str = "d3c7b6a94918a9c9af14df029ef0cdf6d976a3f1351d41ff9db9a69b753cb016"; type directive = enum i32 { ERROR = 0, diff --git a/lib/ww/syntax/typ.ww b/lib/ww/syntax/typ.ww index b6a28dd5..136c8d9f 100644 --- a/lib/ww/syntax/typ.ww +++ b/lib/ww/syntax/typ.ww @@ -569,6 +569,13 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = { return true; }; if (k == tykind.TY_NAMED) { return false; }; // nominal: only same ptr + // Enums are nominal (harec: an enum IS its alias type): distinct + // decls are distinct types even with identical storage/members. + // Falling into the primitive tail made ANY two enums equal — the + // variant matcher picked the first enum variant of a union for a + // value of the OTHER enum (live cs!=ww, wrong tag at runtime). + // Same-decl chased ends hit the a == b fast path above. + if (k == tykind.TY_ENUM) { return false; }; if (k == tykind.TY_TAGGED) { // Structural: variant lists match position-by-position, and // the nullable `(*T|void)` fold is part of identity. Mirrors diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 36924435..fd05d9b6 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -3418,6 +3418,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = { }; }; let tn: *syntax.node = mktname(c, "untyped_int"); + // #59.9 twin: a constant-folded enum member is an N_INTLIT + // whose stamp carries the ENUM type; the post-order revisit + // re-derived untyped_int and clobbered it, sending the + // widen-store variant matcher to its first-variant fallback + // (wrong tag, live cs!=ww — cstage cexpr is single-pass and + // keeps the enum). Keep an existing enum stamp; the returned + // tnode contract for callers is unchanged. + let pre: *syntax.tinfo = e.type_: *syntax.tinfo; + if (pre != nil) { + let prech: *syntax.tinfo = tichase(pre); + if (prech != nil) { + if (prech.kind == syntax.tykind.TY_ENUM) { + return tn; + }; + }; + }; e.type_ = tinfofornode(c, tn): *void; return tn; }; diff --git a/test/wcc/data/enum_union_tag/case.ww b/test/wcc/data/enum_union_tag/case.ww new file mode 100644 index 00000000..dcd35a8d --- /dev/null +++ b/test/wcc/data/enum_union_tag/case.ww @@ -0,0 +1,20 @@ +//ww:run-exit 0 +// Two enums in one union: variant tags must resolve nominally. The +// enum-blind typeeq matched ANY enum pair (wwstage widened shape.BALL +// into the color variant, tag 0), and the post-order revisit clobbered +// the folded member's enum stamp to untyped_int (first-variant +// fallback). Both orderings pinned. +package main; +type color = enum { RED, GREEN }; +type shape = enum { BOX, BALL }; +type either = (color | shape); +type either2 = (shape | color); +export fn main() i32 = { + let e: either = shape.BALL; + if (!(e is shape)) { return 1; }; + let f: either2 = color.RED; + if (!(f is color)) { return 2; }; + let g: either = color.GREEN; + if (!(g is color)) { return 3; }; + return 0; +};