From 74195ac4dce330367fc8f38d71990b6eeb2182e1 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 5 Jun 2026 22:09:28 +0900 Subject: [PATCH] =?UTF-8?q?wcc=5Fww/cgen:=20c4=20tyassignableuntyped=20ful?= =?UTF-8?q?l-chase=20=E2=80=94=20structural=20alignment,=20zero=20acceptan?= =?UTF-8?q?ce=20graduations,=20graduations=20ride=20#90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F2a batch-2 c4, per rob's FINAL RULING (i) (banked, .ai/rob-batch2-spec.md): ww-only — the cs twin (cmd/wcc/type.c:369-385) is already harec-shaped since F1 9bd0d8b. Port of ref/harec/src/types.c:989-996 type_is_assignable structure: full dealias of the dst when its chased kind is not tagged (du = tichase(dst); the STR/BOOL/NIL arms read du); the tagged variant drill keeps the ORIGINAL p.type_ and detects a variant's taggedness via the full chase (was one-level — a 2-level alias-tagged variant slipped INTO the recursion); INT/FLOAT/RUNE keep the recursive predicates on the unpeeled dst, exactly as cs. STRUCTURAL ALIGNMENT, ZERO ACCEPTANCE GRADUATIONS, GRADUATIONS RIDE #90: on wwstage no bool/str literal reaches this funnel today — the checker stamps N_TRUE/N_FALSE CONCRETE bool (check.ww:2440) where cs stamps ty_untyped_bool (check.c:1234), the bool member of the #14 N_STRLIT stamp cluster (task #90, filed this train; its metadata carries the graduation obligations: BOTH layers, the stamp AND the flatvariantidxt pass-2 one-level pu.under guard at :2868/#17, must clear before the bound rows flip). The live silent-wrong this would close — `type bb2 = bb1 = bool; let v: (void|bb2) = true; v is bb2` runs cs 0 / ww 1, single-line asm diff (tag store MOVQ $1 vs $0) — therefore does NOT graduate here: the row dies one layer up (pins-follow-the-layer). test 944 (+7 rows): untyped_hold33_pin ((void|T) untyped-int init, the #33 tagged-dst drill HOLD) + untyped_tagdst_2lvl_hold (2-level alias tagged dst) + untyped_str2lvl_ctl (masked twin — the shape fallback rescues str, documents the rescue) + untyped_int2lvl_ctl + untyped_bool1lvl_ctl all K_RUN 0/0; untyped_bool2lvl_bound90 K_RUN_CS BOUND citing #90; untyped_nestvar_bound K_BUILDERR_CS BOUND expected-state row (#199-alpha + #90) — untyped literal into a NESTED-tagged alias-wrapped variant: cs CHECKER loud-rejects ("not assignable", the #199-alpha ww-stricter no-transitive-drill rule, type.c:316-324) while ww's node-keyed checker accepts and the binary runs 0 OBSERVED post-chase; the acceptance gate is check.ww (batch-4 aligns ww down; the row flips to loud-BOTH then; ww half pinned via #90 metadata — the harness has no cs-reject+ww-runs mode). Every row's cs column OBSERVED (built+run) at dispatch-SHA cs per the ratified condition; probes at /tmp/b2/c4. 989 lib-byteid ratchet: ZERO flips (31 ID / 9 DIVERGE / 3 WWREJECT hold exactly as pinned). Byte-id: five-mains NEUTRAL on identical inputs vs the c3 build; cs==ww on the regenerated five; test-unit 288 green. combined.ww regens ride along. --- selfhost/cmd/w6c/main.combined.ww | 44 +++++++------- selfhost/cmd/wcc/cgenutil.ww | 44 +++++++------- selfhost/cmd/wwdump/main.combined.ww | 44 +++++++------- test/wcc/944_alias_accept_run.c | 87 ++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 60 deletions(-) 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