diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 5b0a1176..774b9e84 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -7722,6 +7722,31 @@ fn isuntypednil(t: *node) bool = { return streq(t.str, "untyped_nil"); }; +// isinttypeast — int-typed AST node. Either a primitive int name +// (i8..i64/u8..u64/int/uint/uintptr/rune) or an N_TENUM. Floats are +// excluded so the enum↔int reinterpret in checkisas (#52) refuses a +// surprise `enum as f64` shape. Mirrors cstage's type_isint +// (cmd/wcc/type.c) restricted to the kinds reachable from AST. +fn isinttypeast(t: *node) bool = { + if (t == nil) { return false; }; + if (t.kind == nkind.N_TENUM) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let s: str = t.str; + if (streq(s, "i8")) { return true; }; + if (streq(s, "i16")) { return true; }; + if (streq(s, "i32")) { return true; }; + if (streq(s, "i64")) { return true; }; + if (streq(s, "u8")) { return true; }; + if (streq(s, "u16")) { return true; }; + if (streq(s, "u32")) { return true; }; + if (streq(s, "u64")) { return true; }; + if (streq(s, "int")) { return true; }; + if (streq(s, "uint")) { return true; }; + if (streq(s, "uintptr")) { return true; }; + if (streq(s, "rune")) { return true; }; + return false; +}; + fn isnumerictname(t: *node) bool = { if (t == nil) { return false; }; if (t.kind != nkind.N_TNAME) { return false; }; @@ -8095,6 +8120,24 @@ fn checkisas(c: *checker, n: *node) void = { let st: *node = scruttype(c, n.lhs); let u: *node = resolvealias(c, unwrapbang(st)); if (u == nil) { return; }; + // #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`). + // Mirrors cstage cmd/wcc/check.c:1346-1357 — N_TYPEASSERT with an + // enum on either side and integer types on both reinterprets in + // the same register, no tag check involved. Returns early before + // the tagged-union gate so lib/time/instant.ww `(c as i32)` and + // the lib/os syscall casts stop false-positiving. `is` (TYPETEST) + // stays rejected on non-tagged operands — cstage cmd/wcc/check.c + // gates the bypass on N_TYPEASSERT only. + if (n.kind == nkind.N_TYPEASSERT) { + let v: *node = resolvealias(c, unwrapbang(n.rhs)); + let lhsenum: bool = false; + let rhsenum: bool = false; + if (u != nil) { if (u.kind == nkind.N_TENUM) { lhsenum = true; }; }; + if (v != nil) { if (v.kind == nkind.N_TENUM) { rhsenum = true; }; }; + if (lhsenum || rhsenum) { + if (isinttypeast(u)) { if (isinttypeast(v)) { return; }; }; + }; + }; if (u.kind != nkind.N_TTAGGED) { os.write(2, "is/as: operand is not a tagged union\n".ptr, 37u64); c.errs += 1; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 9fbe167a..38465d5b 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -728,6 +728,31 @@ fn isuntypednil(t: *node) bool = { return streq(t.str, "untyped_nil"); }; +// isinttypeast — int-typed AST node. Either a primitive int name +// (i8..i64/u8..u64/int/uint/uintptr/rune) or an N_TENUM. Floats are +// excluded so the enum↔int reinterpret in checkisas (#52) refuses a +// surprise `enum as f64` shape. Mirrors cstage's type_isint +// (cmd/wcc/type.c) restricted to the kinds reachable from AST. +fn isinttypeast(t: *node) bool = { + if (t == nil) { return false; }; + if (t.kind == nkind.N_TENUM) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let s: str = t.str; + if (streq(s, "i8")) { return true; }; + if (streq(s, "i16")) { return true; }; + if (streq(s, "i32")) { return true; }; + if (streq(s, "i64")) { return true; }; + if (streq(s, "u8")) { return true; }; + if (streq(s, "u16")) { return true; }; + if (streq(s, "u32")) { return true; }; + if (streq(s, "u64")) { return true; }; + if (streq(s, "int")) { return true; }; + if (streq(s, "uint")) { return true; }; + if (streq(s, "uintptr")) { return true; }; + if (streq(s, "rune")) { return true; }; + return false; +}; + fn isnumerictname(t: *node) bool = { if (t == nil) { return false; }; if (t.kind != nkind.N_TNAME) { return false; }; @@ -1101,6 +1126,24 @@ fn checkisas(c: *checker, n: *node) void = { let st: *node = scruttype(c, n.lhs); let u: *node = resolvealias(c, unwrapbang(st)); if (u == nil) { return; }; + // #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`). + // Mirrors cstage cmd/wcc/check.c:1346-1357 — N_TYPEASSERT with an + // enum on either side and integer types on both reinterprets in + // the same register, no tag check involved. Returns early before + // the tagged-union gate so lib/time/instant.ww `(c as i32)` and + // the lib/os syscall casts stop false-positiving. `is` (TYPETEST) + // stays rejected on non-tagged operands — cstage cmd/wcc/check.c + // gates the bypass on N_TYPEASSERT only. + if (n.kind == nkind.N_TYPEASSERT) { + let v: *node = resolvealias(c, unwrapbang(n.rhs)); + let lhsenum: bool = false; + let rhsenum: bool = false; + if (u != nil) { if (u.kind == nkind.N_TENUM) { lhsenum = true; }; }; + if (v != nil) { if (v.kind == nkind.N_TENUM) { rhsenum = true; }; }; + if (lhsenum || rhsenum) { + if (isinttypeast(u)) { if (isinttypeast(v)) { return; }; }; + }; + }; if (u.kind != nkind.N_TTAGGED) { os.write(2, "is/as: operand is not a tagged union\n".ptr, 37u64); c.errs += 1; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 29eefdfc..7b36abb0 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -7722,6 +7722,31 @@ fn isuntypednil(t: *node) bool = { return streq(t.str, "untyped_nil"); }; +// isinttypeast — int-typed AST node. Either a primitive int name +// (i8..i64/u8..u64/int/uint/uintptr/rune) or an N_TENUM. Floats are +// excluded so the enum↔int reinterpret in checkisas (#52) refuses a +// surprise `enum as f64` shape. Mirrors cstage's type_isint +// (cmd/wcc/type.c) restricted to the kinds reachable from AST. +fn isinttypeast(t: *node) bool = { + if (t == nil) { return false; }; + if (t.kind == nkind.N_TENUM) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let s: str = t.str; + if (streq(s, "i8")) { return true; }; + if (streq(s, "i16")) { return true; }; + if (streq(s, "i32")) { return true; }; + if (streq(s, "i64")) { return true; }; + if (streq(s, "u8")) { return true; }; + if (streq(s, "u16")) { return true; }; + if (streq(s, "u32")) { return true; }; + if (streq(s, "u64")) { return true; }; + if (streq(s, "int")) { return true; }; + if (streq(s, "uint")) { return true; }; + if (streq(s, "uintptr")) { return true; }; + if (streq(s, "rune")) { return true; }; + return false; +}; + fn isnumerictname(t: *node) bool = { if (t == nil) { return false; }; if (t.kind != nkind.N_TNAME) { return false; }; @@ -8095,6 +8120,24 @@ fn checkisas(c: *checker, n: *node) void = { let st: *node = scruttype(c, n.lhs); let u: *node = resolvealias(c, unwrapbang(st)); if (u == nil) { return; }; + // #52: enum ↔ int reinterpret (`enum as intT` / `intT as enum`). + // Mirrors cstage cmd/wcc/check.c:1346-1357 — N_TYPEASSERT with an + // enum on either side and integer types on both reinterprets in + // the same register, no tag check involved. Returns early before + // the tagged-union gate so lib/time/instant.ww `(c as i32)` and + // the lib/os syscall casts stop false-positiving. `is` (TYPETEST) + // stays rejected on non-tagged operands — cstage cmd/wcc/check.c + // gates the bypass on N_TYPEASSERT only. + if (n.kind == nkind.N_TYPEASSERT) { + let v: *node = resolvealias(c, unwrapbang(n.rhs)); + let lhsenum: bool = false; + let rhsenum: bool = false; + if (u != nil) { if (u.kind == nkind.N_TENUM) { lhsenum = true; }; }; + if (v != nil) { if (v.kind == nkind.N_TENUM) { rhsenum = true; }; }; + if (lhsenum || rhsenum) { + if (isinttypeast(u)) { if (isinttypeast(v)) { return; }; }; + }; + }; if (u.kind != nkind.N_TTAGGED) { os.write(2, "is/as: operand is not a tagged union\n".ptr, 37u64); c.errs += 1;