From 56aac85f6f3b4b54d0c913b14fb7c2384686e1ac Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 6 Jun 2026 07:43:39 +0900 Subject: [PATCH] =?UTF-8?q?cgen:=20#95=20c2=20structural=20variant=20fallb?= =?UTF-8?q?ack=20=E2=80=94=20both-stage=20fused?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A nominally-unrelated, structurally-equal NAMED source into a NAMED variant (kb95_unrel: ta/tb same-layout structs, src ta -> (void|tb)) was LIVE both-wrong-identical byte-id silent: both checkers accept, both cgens tagged 0. After c1's chain arm finds no shared chain node, match the variant whose CHASED type type_eq's/typeeq's the source's chased bottom — chased type EQUALITY only, no type_is_assignable scalar import, no int widening (ken's binding scalar warning). Same NAMED-source branch, both stages (cg_tag_for_variant / flatvariantidxt), forced fuse. Correctness reference, cite 1 — harec tagged_select_subtype P2+P3 (ref/harec/src/types.c:702-739), verbatim: if (t->id == subtype->id) { return t; } if (type_is_assignable(ctx, t, subtype)) { selected = t; ++nassign; } ... if (nassign == 1) { return selected; } return NULL; with type_is_assignable's non-tagged path dealiasing both sides and accepting composites only via interned pointer equality (types.c:988-1002), verbatim: if (type_dealias(ctx, to)->storage != STORAGE_TAGGED) { to = type_dealias(ctx, to); from = type_dealias(ctx, from); } ... if (to == from && to->storage != STORAGE_VOID) { return true; } Cite 2 — type_hash interns bare composites STRUCTURALLY (banked as types.c:72-81; verified in the vendored copy at types.c:444 + struct/union arm :514-525), verbatim: case STORAGE_UNION: hash = fnv1a_size(hash, type->struct_union.packed); for (const struct struct_field *field = type->struct_union.fields; field; field = field->next) { if (field->name) { hash = fnv1a_s(hash, field->name); } hash = fnv1a_u32(hash, type_hash(field->type)); hash = fnv1a_size(hash, field->offset); } — no decl ident in the hash, so harec's two decls dealias to ONE interned node and `to == from` holds: acceptance is DEFINITIONAL under interning, not an arm whose text could be misread. Our store does not intern; chased type equality is the non-interned rendering of the same rule. Honest divergence (the >=2-structural-match hard-error STAYS): under harec's interning two structurally-identical variants are ONE type — a union cannot contain it twice — so the ambiguity case is unrepresentable there; our hard-error (twin texts, shared tail "source structurally matches >=2 variants — ambiguous without nominal layout (#95)") is the correct nominal-lossy-model rendering, not a harec deviation. Pin table: unrel_struct row added (kb95_unrel graduates ok/1-ok/1 -> 0/0, byte-id held) — suite now 48/48. All c1 rows unmoved. Invariants: 163-row dissolution matrix at tip — same 3 family graduations as c1, ZERO new movers; five mains cs-vs-ww byte-id OK; make all 0; sizelint 0; peellint 0 (no new peel sites — the structural leg reads only chased ends); all 944 suites + 808 green. w6c_ww/wwdump_ww main.combined.ww regen'd. --- cmd/w6c/cgen.c | 27 ++++++++++++++++++++++++ selfhost/cmd/w6c/main.combined.ww | 31 ++++++++++++++++++++++++++++ selfhost/cmd/wcc/cgenutil.ww | 31 ++++++++++++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 31 ++++++++++++++++++++++++++++ test/wcc/944_variant_chain_b95_run.c | 14 +++++++++++++ 5 files changed, 134 insertions(+) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 2f747cda..1b6c31e6 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -921,6 +921,33 @@ cg_tag_for_variant(Type *t, Type *vt) fatal("cg_tag_for_variant: source alias chain " "reaches >=2 variants — ambiguous without " "nominal layout (#95)"); + if (found >= 0) return found; + /* c2 (#95): no chain hit (found/n are still -1/0 here) — + * structural fallback on the chased ends. harec interns + * bare composites structurally (type_hash, ref/harec/src/ + * types.c:72-81), so a nominally-unrelated structurally- + * equal decl DEALIASES TO THE SAME NODE there and the + * assignability arm accepts via `to == from` + * (types.c:1000-1002) — acceptance is definitional, not an + * arm we could misread. Our store does not intern, so the + * pointer compare of pass 1b misses it; chased type_eq is + * the non-interned rendering of the same rule. EQUALITY + * only — no type_is_assignable scalar import. The >=2 + * hard-error is the nominal-lossy-model rendering of a case + * harec cannot represent (two structurally-identical + * variants intern to ONE type — a union cannot contain it + * twice), not a harec deviation. */ + idx = 0; + for (Tparam *p = t->params; p; p = p->next, idx++) { + if (p->type && type_eq(type_chase_named(p->type), sb)) { + if (found < 0) found = idx; + n++; + } + } + if (n >= 2) + fatal("cg_tag_for_variant: source structurally " + "matches >=2 variants — ambiguous without " + "nominal layout (#95)"); return found; } /* Pass 2 (#15): no exact variant matched — try a structural match of diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index faccf5f9..dee27fc7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -19021,6 +19021,37 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; + if (found >= 0) { return found; }; + // c2 (#95): no chain hit (found/n are still -1/0 here) — + // structural fallback on the chased ends. harec interns bare + // composites structurally (type_hash, ref/harec/src/ + // types.c:72-81), so a nominally-unrelated structurally- + // equal decl DEALIASES TO THE SAME NODE there and the + // assignability arm accepts via `to == from` + // (types.c:1000-1002) — acceptance is definitional, not an + // arm we could misread. Our store does not intern, so the + // pointer compare of pass 1b misses it; chased typeeq is the + // non-interned rendering of the same rule. EQUALITY only — + // no type_is_assignable scalar import. The >=2 hard-error is + // the nominal-lossy-model rendering of a case harec cannot + // represent (two structurally-identical variants intern to + // ONE type — a union cannot contain it twice), not a harec + // deviation. cs twin cg_tag_for_variant fused in this commit. + q = ti.params; + qi = 0; + for (q != nil) { + if (q.type_ != nil && typeeq(tichase(q.type_), sb)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg2: str = "flatvariantidxt: source structurally matches >=2 variants — ambiguous without nominal layout (#95)\n"; + os.write(2, msg2.ptr, msg2.len: u64); + os.exit(1); + }; return found; }; // Pass 2 (#15): no exact variant matched — structurally match a BARE diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 89fa70de..9be1a9a2 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -2917,6 +2917,37 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; + if (found >= 0) { return found; }; + // c2 (#95): no chain hit (found/n are still -1/0 here) — + // structural fallback on the chased ends. harec interns bare + // composites structurally (type_hash, ref/harec/src/ + // types.c:72-81), so a nominally-unrelated structurally- + // equal decl DEALIASES TO THE SAME NODE there and the + // assignability arm accepts via `to == from` + // (types.c:1000-1002) — acceptance is definitional, not an + // arm we could misread. Our store does not intern, so the + // pointer compare of pass 1b misses it; chased typeeq is the + // non-interned rendering of the same rule. EQUALITY only — + // no type_is_assignable scalar import. The >=2 hard-error is + // the nominal-lossy-model rendering of a case harec cannot + // represent (two structurally-identical variants intern to + // ONE type — a union cannot contain it twice), not a harec + // deviation. cs twin cg_tag_for_variant fused in this commit. + q = ti.params; + qi = 0; + for (q != nil) { + if (q.type_ != nil && typeeq(tichase(q.type_), sb)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg2: str = "flatvariantidxt: source structurally matches >=2 variants — ambiguous without nominal layout (#95)\n"; + os.write(2, msg2.ptr, msg2.len: u64); + os.exit(1); + }; return found; }; // Pass 2 (#15): no exact variant matched — structurally match a BARE diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 762262b9..2df463f2 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -19021,6 +19021,37 @@ fn flatvariantidxt(tagged: *tinfo, want: *tinfo) i32 = { os.write(2, msg.ptr, msg.len: u64); os.exit(1); }; + if (found >= 0) { return found; }; + // c2 (#95): no chain hit (found/n are still -1/0 here) — + // structural fallback on the chased ends. harec interns bare + // composites structurally (type_hash, ref/harec/src/ + // types.c:72-81), so a nominally-unrelated structurally- + // equal decl DEALIASES TO THE SAME NODE there and the + // assignability arm accepts via `to == from` + // (types.c:1000-1002) — acceptance is definitional, not an + // arm we could misread. Our store does not intern, so the + // pointer compare of pass 1b misses it; chased typeeq is the + // non-interned rendering of the same rule. EQUALITY only — + // no type_is_assignable scalar import. The >=2 hard-error is + // the nominal-lossy-model rendering of a case harec cannot + // represent (two structurally-identical variants intern to + // ONE type — a union cannot contain it twice), not a harec + // deviation. cs twin cg_tag_for_variant fused in this commit. + q = ti.params; + qi = 0; + for (q != nil) { + if (q.type_ != nil && typeeq(tichase(q.type_), sb)) { + if (found < 0) { found = qi; }; + n += 1; + }; + q = q.tnext; + qi += 1; + }; + if (n >= 2) { + let msg2: str = "flatvariantidxt: source structurally matches >=2 variants — ambiguous without nominal layout (#95)\n"; + os.write(2, msg2.ptr, msg2.len: u64); + os.exit(1); + }; return found; }; // Pass 2 (#15): no exact variant matched — structurally match a BARE diff --git a/test/wcc/944_variant_chain_b95_run.c b/test/wcc/944_variant_chain_b95_run.c index 09be5d37..d31a5945 100644 --- a/test/wcc/944_variant_chain_b95_run.c +++ b/test/wcc/944_variant_chain_b95_run.c @@ -359,6 +359,20 @@ static const struct row rows[] = { " return 0;\n" "};\n", 0, 0, K_RUN_CS_WWERR, "deferred #277" }, /* ww: task #277 */ + /* ---- c2: structural fallback — was both-wrong-identical + * byte-id silent (live metric-1: checkers accept, cgen tagged + * 0). harec accepts by interning (one node, tag tb). */ + { "unrel_struct", + "package main;\n" + "type ta = struct { a: int, b: int };\n" + "type tb = struct { a: int, b: int };\n" + "export fn main() i32 = {\n" + " let s: ta;\n" + " s.a = 4; s.b = 9;\n" + " let v: (void | tb) = s;\n" + " if (!(v is tb)) { return 1; };\n" + " return 0;\n" + "};\n", 0, 0, K_RUN, NULL }, }; static int