diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 72a15669..ace64eda 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -18990,17 +18990,26 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { if (dst == nil) { return false; }; if (want == nil) { return false; }; - let du: *tinfo = dst; - if (du.kind == tykind.TY_NAMED && du.under != nil) { du = du.under; }; - if (du.kind == tykind.TY_TAGGED) { + // c4 (rob RE-RULE 2026-06-05): FULL chase, not the one-level unwrap + // — harec type_is_assignable dealiases the dst transitively when it + // is not tagged and keeps the ORIGINAL dst for the tagged variant + // drill, taggedness detected via the full chase (ref/harec/src/ + // types.c:989-996). The one-level unwrap left a 2-level named bool + // alias variant silently mis-tagged 0 (the str twin was masked by + // taggedvariantindext's shape fallback). Aligns ww UP to the cs + // twin cmd/wcc/type.c:369-385, harec-shaped since F1 9bd0d8b. + let du: *tinfo = tichase(dst); + if (du != nil && du.kind == tykind.TY_TAGGED) { // concrete→tagged drill (type.c:316-324): a NESTED tagged // variant compares by type_eq there — never equal to an - // untyped source — so only non-tagged variants recurse. + // untyped source — so only non-tagged variants recurse, on + // the variant's ORIGINAL type; the variant's taggedness is + // detected via the full chase (was one-level: a 2-level + // alias-tagged variant slipped INTO the recursion, diverging + // from cs's checker-level #199α reject — see the c4 finding). let p: *tparam = du.params; for (p != nil) { - let pu: *tinfo = p.type_; - if (pu != nil && pu.kind == tykind.TY_NAMED - && pu.under != nil) { pu = pu.under; }; + let pu: *tinfo = tichase(p.type_); let nestedtagged: bool = false; if (pu != nil) { if (pu.kind == tykind.TY_TAGGED) { nestedtagged = true; }; @@ -19012,30 +19021,25 @@ fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { }; return false; }; - // type.c:357-369 — the per-kind holds checks run on the UNPEELED - // dst: typeisnum/typeisfloat/typeisint self-recurse TY_NAMED like - // their cstage twins; STR/BOOL spell out the one-level unwrap. + // type.c:369-385 — INT/FLOAT/RUNE run on the UNPEELED dst exactly + // like cs (typeisnum/typeisfloat/typeisint self-recurse TY_NAMED); + // STR/BOOL/NIL read the chased du like cs reads its chased du. if (want.kind == tykind.TY_UNTYPED_INT) { return typeisnum(dst); }; if (want.kind == tykind.TY_UNTYPED_FLOAT) { return typeisfloat(dst); }; if (want.kind == tykind.TY_UNTYPED_STR) { - if (dst.kind == tykind.TY_STR) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_STR; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_STR; }; if (want.kind == tykind.TY_UNTYPED_RUNE) { if (typeisint(dst)) { return true; }; return dst.kind == tykind.TY_RUNE; }; if (want.kind == tykind.TY_UNTYPED_BOOL) { - if (dst.kind == tykind.TY_BOOL) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_BOOL; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_BOOL; }; if (want.kind == tykind.TY_UNTYPED_NIL) { + if (du == nil) { return false; }; if (du.kind == tykind.TY_PTR) { return true; }; if (du.kind == tykind.TY_SLICE) { return true; }; if (du.kind == tykind.TY_CHAN) { return true; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index eb45286b..108d10da 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -2918,17 +2918,26 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { if (dst == nil) { return false; }; if (want == nil) { return false; }; - let du: *tinfo = dst; - if (du.kind == tykind.TY_NAMED && du.under != nil) { du = du.under; }; - if (du.kind == tykind.TY_TAGGED) { + // c4 (rob RE-RULE 2026-06-05): FULL chase, not the one-level unwrap + // — harec type_is_assignable dealiases the dst transitively when it + // is not tagged and keeps the ORIGINAL dst for the tagged variant + // drill, taggedness detected via the full chase (ref/harec/src/ + // types.c:989-996). The one-level unwrap left a 2-level named bool + // alias variant silently mis-tagged 0 (the str twin was masked by + // taggedvariantindext's shape fallback). Aligns ww UP to the cs + // twin cmd/wcc/type.c:369-385, harec-shaped since F1 9bd0d8b. + let du: *tinfo = tichase(dst); + if (du != nil && du.kind == tykind.TY_TAGGED) { // concrete→tagged drill (type.c:316-324): a NESTED tagged // variant compares by type_eq there — never equal to an - // untyped source — so only non-tagged variants recurse. + // untyped source — so only non-tagged variants recurse, on + // the variant's ORIGINAL type; the variant's taggedness is + // detected via the full chase (was one-level: a 2-level + // alias-tagged variant slipped INTO the recursion, diverging + // from cs's checker-level #199α reject — see the c4 finding). let p: *tparam = du.params; for (p != nil) { - let pu: *tinfo = p.type_; - if (pu != nil && pu.kind == tykind.TY_NAMED - && pu.under != nil) { pu = pu.under; }; + let pu: *tinfo = tichase(p.type_); let nestedtagged: bool = false; if (pu != nil) { if (pu.kind == tykind.TY_TAGGED) { nestedtagged = true; }; @@ -2940,30 +2949,25 @@ fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { }; return false; }; - // type.c:357-369 — the per-kind holds checks run on the UNPEELED - // dst: typeisnum/typeisfloat/typeisint self-recurse TY_NAMED like - // their cstage twins; STR/BOOL spell out the one-level unwrap. + // type.c:369-385 — INT/FLOAT/RUNE run on the UNPEELED dst exactly + // like cs (typeisnum/typeisfloat/typeisint self-recurse TY_NAMED); + // STR/BOOL/NIL read the chased du like cs reads its chased du. if (want.kind == tykind.TY_UNTYPED_INT) { return typeisnum(dst); }; if (want.kind == tykind.TY_UNTYPED_FLOAT) { return typeisfloat(dst); }; if (want.kind == tykind.TY_UNTYPED_STR) { - if (dst.kind == tykind.TY_STR) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_STR; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_STR; }; if (want.kind == tykind.TY_UNTYPED_RUNE) { if (typeisint(dst)) { return true; }; return dst.kind == tykind.TY_RUNE; }; if (want.kind == tykind.TY_UNTYPED_BOOL) { - if (dst.kind == tykind.TY_BOOL) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_BOOL; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_BOOL; }; if (want.kind == tykind.TY_UNTYPED_NIL) { + if (du == nil) { return false; }; if (du.kind == tykind.TY_PTR) { return true; }; if (du.kind == tykind.TY_SLICE) { return true; }; if (du.kind == tykind.TY_CHAN) { return true; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 0ea6fce9..c6d78359 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -18990,17 +18990,26 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { if (dst == nil) { return false; }; if (want == nil) { return false; }; - let du: *tinfo = dst; - if (du.kind == tykind.TY_NAMED && du.under != nil) { du = du.under; }; - if (du.kind == tykind.TY_TAGGED) { + // c4 (rob RE-RULE 2026-06-05): FULL chase, not the one-level unwrap + // — harec type_is_assignable dealiases the dst transitively when it + // is not tagged and keeps the ORIGINAL dst for the tagged variant + // drill, taggedness detected via the full chase (ref/harec/src/ + // types.c:989-996). The one-level unwrap left a 2-level named bool + // alias variant silently mis-tagged 0 (the str twin was masked by + // taggedvariantindext's shape fallback). Aligns ww UP to the cs + // twin cmd/wcc/type.c:369-385, harec-shaped since F1 9bd0d8b. + let du: *tinfo = tichase(dst); + if (du != nil && du.kind == tykind.TY_TAGGED) { // concrete→tagged drill (type.c:316-324): a NESTED tagged // variant compares by type_eq there — never equal to an - // untyped source — so only non-tagged variants recurse. + // untyped source — so only non-tagged variants recurse, on + // the variant's ORIGINAL type; the variant's taggedness is + // detected via the full chase (was one-level: a 2-level + // alias-tagged variant slipped INTO the recursion, diverging + // from cs's checker-level #199α reject — see the c4 finding). let p: *tparam = du.params; for (p != nil) { - let pu: *tinfo = p.type_; - if (pu != nil && pu.kind == tykind.TY_NAMED - && pu.under != nil) { pu = pu.under; }; + let pu: *tinfo = tichase(p.type_); let nestedtagged: bool = false; if (pu != nil) { if (pu.kind == tykind.TY_TAGGED) { nestedtagged = true; }; @@ -19012,30 +19021,25 @@ fn tyassignableuntyped(dst: *tinfo, want: *tinfo) bool = { }; return false; }; - // type.c:357-369 — the per-kind holds checks run on the UNPEELED - // dst: typeisnum/typeisfloat/typeisint self-recurse TY_NAMED like - // their cstage twins; STR/BOOL spell out the one-level unwrap. + // type.c:369-385 — INT/FLOAT/RUNE run on the UNPEELED dst exactly + // like cs (typeisnum/typeisfloat/typeisint self-recurse TY_NAMED); + // STR/BOOL/NIL read the chased du like cs reads its chased du. if (want.kind == tykind.TY_UNTYPED_INT) { return typeisnum(dst); }; if (want.kind == tykind.TY_UNTYPED_FLOAT) { return typeisfloat(dst); }; if (want.kind == tykind.TY_UNTYPED_STR) { - if (dst.kind == tykind.TY_STR) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_STR; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_STR; }; if (want.kind == tykind.TY_UNTYPED_RUNE) { if (typeisint(dst)) { return true; }; return dst.kind == tykind.TY_RUNE; }; if (want.kind == tykind.TY_UNTYPED_BOOL) { - if (dst.kind == tykind.TY_BOOL) { return true; }; - if (dst.kind == tykind.TY_NAMED && dst.under != nil) { - return dst.under.kind == tykind.TY_BOOL; - }; - return false; + if (du == nil) { return false; }; + return du.kind == tykind.TY_BOOL; }; if (want.kind == tykind.TY_UNTYPED_NIL) { + if (du == nil) { return false; }; if (du.kind == tykind.TY_PTR) { return true; }; if (du.kind == tykind.TY_SLICE) { return true; }; if (du.kind == tykind.TY_CHAN) { return true; }; diff --git a/test/wcc/944_alias_accept_run.c b/test/wcc/944_alias_accept_run.c index f4347076..95e93859 100644 --- a/test/wcc/944_alias_accept_run.c +++ b/test/wcc/944_alias_accept_run.c @@ -797,6 +797,93 @@ static const struct row rows[] = { " if (y != 70000) { return 1; };\n" " return 0;\n" "};\n", 0, K_RUN, NULL }, + /* ---- F2a batch-2 c4 (rob FINAL RULING (i)): tyassignableuntyped + * full-chase — STRUCTURAL alignment to cs type.c:369-385 / harec + * type_is_assignable; ZERO acceptance graduations in this commit + * (graduations ride task #90). Holds + controls pin the chase as + * behavior-neutral on every reachable shape; the tagged-dst rows + * are the #33 pins (drill semantics unchanged). */ + { "untyped_hold33_pin", /* (void|T) untyped-int init — #33 fixed + * behavior, tagged-dst drill HOLD */ + "package main;\n" + "export fn main() i32 = {\n" + " let mn: (void | size) = 5;\n" + " if (!(mn is size)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "untyped_tagdst_2lvl_hold", /* 2-level alias TAGGED dst holds */ + "package main;\n" + "type r1 = (void | size);\n" + "type r2 = r1;\n" + "export fn main() i32 = {\n" + " let v: r2 = 5;\n" + " if (!(v is size)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "untyped_str2lvl_ctl", /* masked twin: ww stamps the str + * literal CONCRETE (#14 cluster), the + * shape fallback rescues the tag — 0/0 + * all along; documents the rescue */ + "package main;\n" + "type sa1 = str;\n" + "type sa2 = sa1;\n" + "export fn main() i32 = {\n" + " let v: (void | sa2) = \"hi\";\n" + " if (!(v is sa2)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "untyped_int2lvl_ctl", /* typeisnum NAMED-recursion control */ + "package main;\n" + "type ia1 = i64;\n" + "type ia2 = ia1;\n" + "export fn main() i32 = {\n" + " let v: (void | ia2) = 7;\n" + " if (!(v is ia2)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + { "untyped_bool1lvl_ctl", /* 1 level: ww passes via the + * fallback's one peel — 0/0 observed */ + "package main;\n" + "type ba1 = bool;\n" + "export fn main() i32 = {\n" + " let v: (void | ba1) = true;\n" + " if (!(v is ba1)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* BOUND row (task #90): ww stamps N_TRUE CONCRETE bool + * (check.ww:2440) vs cs untyped_bool (check.c:1234), so the bool + * literal never reaches the c4-chased untyped funnel on ww — it + * dies at flatvariantidxt pass-2's one-level pu.under guard + * (cgenutil.ww:2868, the #17 site): SILENT tag 0, ww runs exit 1 + * where cs runs 0. Flip to K_RUN when #90 clears BOTH layers + * (stamp + fallback peel). */ + { "untyped_bool2lvl_bound90", + "package main;\n" + "type bb1 = bool;\n" + "type bb2 = bb1;\n" + "export fn main() i32 = {\n" + " let v: (void | bb2) = true;\n" + " if (!(v is bb2)) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN_CS, NULL }, /* ww: task #90 */ + /* BOUND expected-state row (#199α + #90): untyped literal into a + * NESTED-tagged alias-wrapped variant. cs CHECKER loud-rejects — + * the #199α ww-stricter no-transitive-drill rule (type.c:316-324); + * ww's node-keyed checker ACCEPTS and the binary runs exit 0 + * (observed post-c4; the harness has no cs-reject+ww-runs mode, + * so the ww half is pinned via #90's metadata, not here). The c4 + * pu-chase aligned the CGEN drill structure to cs; the acceptance + * gate is check.ww — batch-4 aligns ww down, then this row flips + * to K_BUILDERR (loud BOTH). */ + { "untyped_nestvar_bound", + "package main;\n" + "type inner = (i64 | str);\n" + "type inner2 = inner;\n" + "export fn main() i32 = {\n" + " let v: (void | inner2) = 7;\n" + " if (v is void) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_BUILDERR_CS, "not assignable" }, /* ---- task #71 (rob probe-ruled F1 enrollment): nested fields * whose TYPES are aliases. The chained-dot store/read/addr-of * walks single-peeled each hop, aborted on the alias, and fell