wcc: enums compare nominally; folded members keep their enum stamp
Two composing defects made a two-enum union mis-tag in wwstage (live cs!=ww: `let e: (color|shape) = shape.BALL` stored tag 0 — the color arm — while cstage stored 1). type_eq/typeeq had no TY_ENUM arm, so ANY two enums fell into the primitive default and compared equal; enums are nominal (harec: an enum IS its alias type) and now compare by node identity only. Underneath, the wwstage post-order revisit re-stamped the constant-folded enum member (an N_INTLIT) as untyped_int, clobbering the enum stamp the N_DOT fold applied, so the widen matcher fell to its first-variant fallback -- the #59.9 N_BIN guard now twins on N_INTLIT (cstage cexpr is single-pass and never clobbered).
This commit is contained in:
@@ -275,6 +275,15 @@ type_eq(Type *a, Type *b)
|
|||||||
}
|
}
|
||||||
case TY_NAMED:
|
case TY_NAMED:
|
||||||
return a == b; /* nominally equal only when same node */
|
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: {
|
case TY_TUPLE: {
|
||||||
Tparam *pa = a->params, *pb = b->params;
|
Tparam *pa = a->params, *pb = b->params;
|
||||||
while (pa && pb) {
|
while (pa && pb) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package wwfixture;
|
package wwfixture;
|
||||||
|
|
||||||
def protocolversion: i32 = 1;
|
def protocolversion: i32 = 1;
|
||||||
def corpuscount: i32 = 1751;
|
def corpuscount: i32 = 1752;
|
||||||
def errorcount: i32 = 349;
|
def errorcount: i32 = 349;
|
||||||
def compilecount: i32 = 21;
|
def compilecount: i32 = 21;
|
||||||
def runcount: i32 = 209;
|
def runcount: i32 = 209;
|
||||||
def runexitcount: i32 = 1172;
|
def runexitcount: i32 = 1173;
|
||||||
def nativecount: i32 = 3502;
|
def nativecount: i32 = 3504;
|
||||||
def corpushash: str = "0d0401f0d80ca682b0872343a62db4f91c0ff4c68e71f97cd5358cf92faffebc";
|
def corpushash: str = "d3c7b6a94918a9c9af14df029ef0cdf6d976a3f1351d41ff9db9a69b753cb016";
|
||||||
|
|
||||||
type directive = enum i32 {
|
type directive = enum i32 {
|
||||||
ERROR = 0,
|
ERROR = 0,
|
||||||
|
|||||||
@@ -569,6 +569,13 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
if (k == tykind.TY_NAMED) { return false; }; // nominal: only same ptr
|
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) {
|
if (k == tykind.TY_TAGGED) {
|
||||||
// Structural: variant lists match position-by-position, and
|
// Structural: variant lists match position-by-position, and
|
||||||
// the nullable `(*T|void)` fold is part of identity. Mirrors
|
// the nullable `(*T|void)` fold is part of identity. Mirrors
|
||||||
|
|||||||
@@ -3418,6 +3418,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
let tn: *syntax.node = mktname(c, "untyped_int");
|
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;
|
e.type_ = tinfofornode(c, tn): *void;
|
||||||
return tn;
|
return tn;
|
||||||
};
|
};
|
||||||
|
|||||||
20
test/wcc/data/enum_union_tag/case.ww
Normal file
20
test/wcc/data/enum_union_tag/case.ww
Normal file
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user