From 1e081e28c69599bbf7fd248e82497905a3ddc92e Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 3 Jun 2026 22:51:24 +0900 Subject: [PATCH] wcc_ww/check: infer named-struct element for arrlit->slice assignability (fix #19) wwstage over-rejected an inline `let g: []pt = [pt{..}, pt{..}]`. The N_ARRLIT exprtype arm inferred its element type from the first element, an N_STRUCTLIT, whose exprtype arm deliberately returns the struct BODY (N_TSTRUCT) per #66. The array->slice isassignable arm then typeeqast's the declared element (N_TNAME "pt") against that body and bails on the TNAME-vs-TSTRUCT kind mismatch -> confident-false -> reject. The reject is a KIND mismatch, not a nominal-compare weakness: typeeqast is already streq-keyed for N_TNAME. Narrow fix: when the first arrlit element is a named struct literal, capture the NAMED type (mktname) so su.lhs matches the declared N_TNAME shape, mirroring cstage's element inference. typeeqast and the N_STRUCTLIT #66 body-return are untouched; non-named elements keep the existing first-element shape. e.type_ via tinfofornode still resolves [N]pt for cgen, so cstage/wwstage stay byte-identical. Test 687 gains struct_pt (sum+len+cap == 14), struct_3f (mixed-width u8/i64/i32 field offsets == 23), and struct_arrvar_local (the array-VARIABLE form still accepts+runs; local scope since the module-scope variable form is the deferred #22 link gap). --- selfhost/cmd/w6c/main.combined.ww | 20 +++++++++- selfhost/cmd/wcc/check.ww | 20 +++++++++- selfhost/cmd/wwdump/main.combined.ww | 20 +++++++++- test/wcc/687_slice_literal_global.c | 59 ++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 72324bc1..e5812fa7 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -13296,7 +13296,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; if (!skip) { let t: *node = exprtype(c, it, nil); - if (elt == nil) { elt = t; }; + if (elt == nil) { + // #19: N_STRUCTLIT exprtype returns the struct BODY + // (N_TSTRUCT) per #66, but the array->slice + // isassignable arm typeeqast's the declared element + // N_TNAME against this element shape — a TNAME-vs- + // TSTRUCT kind mismatch reads confident-false and + // rejects. cstage infers the NAMED type here. Capture + // the named type for a named struct literal so su.lhs + // is the same N_TNAME shape (typeeqast is already + // streq-keyed for TNAME). Non-named elements keep the + // existing first-element shape. + if (it.kind == nkind.N_STRUCTLIT + && it.lhs != nil + && it.lhs.kind == nkind.N_IDENT) { + elt = mktname(c, it.lhs.str); + } else { + elt = t; + }; + }; count += 1u64; }; it = it.next; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 04241ea5..6c536446 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -2929,7 +2929,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; if (!skip) { let t: *node = exprtype(c, it, nil); - if (elt == nil) { elt = t; }; + if (elt == nil) { + // #19: N_STRUCTLIT exprtype returns the struct BODY + // (N_TSTRUCT) per #66, but the array->slice + // isassignable arm typeeqast's the declared element + // N_TNAME against this element shape — a TNAME-vs- + // TSTRUCT kind mismatch reads confident-false and + // rejects. cstage infers the NAMED type here. Capture + // the named type for a named struct literal so su.lhs + // is the same N_TNAME shape (typeeqast is already + // streq-keyed for TNAME). Non-named elements keep the + // existing first-element shape. + if (it.kind == nkind.N_STRUCTLIT + && it.lhs != nil + && it.lhs.kind == nkind.N_IDENT) { + elt = mktname(c, it.lhs.str); + } else { + elt = t; + }; + }; count += 1u64; }; it = it.next; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9832cd46..59b5218c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -13296,7 +13296,25 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { }; if (!skip) { let t: *node = exprtype(c, it, nil); - if (elt == nil) { elt = t; }; + if (elt == nil) { + // #19: N_STRUCTLIT exprtype returns the struct BODY + // (N_TSTRUCT) per #66, but the array->slice + // isassignable arm typeeqast's the declared element + // N_TNAME against this element shape — a TNAME-vs- + // TSTRUCT kind mismatch reads confident-false and + // rejects. cstage infers the NAMED type here. Capture + // the named type for a named struct literal so su.lhs + // is the same N_TNAME shape (typeeqast is already + // streq-keyed for TNAME). Non-named elements keep the + // existing first-element shape. + if (it.kind == nkind.N_STRUCTLIT + && it.lhs != nil + && it.lhs.kind == nkind.N_IDENT) { + elt = mktname(c, it.lhs.str); + } else { + elt = t; + }; + }; count += 1u64; }; it = it.next; diff --git a/test/wcc/687_slice_literal_global.c b/test/wcc/687_slice_literal_global.c index 8acd9396..869989f2 100644 --- a/test/wcc/687_slice_literal_global.c +++ b/test/wcc/687_slice_literal_global.c @@ -35,6 +35,18 @@ * i64_lencap | g.len+g.cap | 6 * i32_sum | let g:[]i32=[7,8,9]; g[0]+g[1]+g[2] | 24 * one_edge | let g:[]u8=[42]; g[0]+g.len | 43 + * struct_pt | []pt=[pt{1,2},pt{3,4}]; sum+len+cap | 14 + * struct_3f | []q (u8,i64,i32) two elems; sum+len | 23 + * struct_one | []pt=[pt{5,6}]; sum+len+cap (count==1) | 13 + * struct_arrvar_l | local let arr:[2]pt; let g:[]pt=arr | 14 + * + * #19: wwstage's checker over-rejected the inline NAMED-STRUCT-element + * array->slice (`let g:[]pt=[pt{..},pt{..}]`) — its N_ARRLIT element-type + * inference returned the N_STRUCTLIT body (N_TSTRUCT, per #66) and the + * array->slice isassignable typeeqast saw a TNAME-vs-TSTRUCT kind mismatch + * against the declared N_TNAME element. cstage already inferred the NAMED + * type. Fix: infer the named type for a named struct-literal first element + * (check.ww N_ARRLIT arm); typeeqast untouched. */ #include #include @@ -111,6 +123,53 @@ static const struct row rows[] = { "let g: S = [1u8, 2u8, 3u8];\n" "export fn main() i32 = { return (g[0]:i32)+(g.len:i32)+(g.cap:i32); };\n", 7 }, + /* #19: inline NAMED-STRUCT-element array->slice. wwstage's checker + * over-rejected this — the N_ARRLIT element-type inference picked up + * the N_STRUCTLIT's body (N_TSTRUCT, per #66) and the array->slice + * isassignable typeeqast saw a TNAME-vs-TSTRUCT kind mismatch (the + * declared element is N_TNAME). Fix infers the NAMED element. cstage + * already accepted; emit_array_lit_bytes' struct-field recursion + * makes the backing byte-identical. */ + { "struct_pt", + "package main;\n" + "type pt = struct { a: i64, b: i64 };\n" + "let g: []pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];\n" + "export fn main() i32 = {\n" + "\treturn (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);\n" + "};\n", + 14 }, + /* 3-field mixed-width struct stresses field offsets in the backing + * (u8 @0, i64 @8, i32 @16 — non-uniform strides). */ + { "struct_3f", + "package main;\n" + "type q = struct { a: u8, b: i64, c: i32 };\n" + "let g: []q = [q{a=1u8, b=2i64, c=3i32}, q{a=4u8, b=5i64, c=6i32}];\n" + "export fn main() i32 = {\n" + "\treturn (g[0].a:i32)+(g[0].b:i32)+(g[0].c:i32)+(g[1].a:i32)+(g[1].b:i32)+(g[1].c:i32)+(g.len:i32);\n" + "};\n", + 23 }, + /* single-element []struct (count==1 backing edge). */ + { "struct_one", + "package main;\n" + "type pt = struct { a: i64, b: i64 };\n" + "let g: []pt = [pt{a=5i64, b=6i64}];\n" + "export fn main() i32 = {\n" + "\treturn (g[0].a:i32)+(g[0].b:i32)+(g.len:i32)+(g.cap:i32);\n" + "};\n", + 13 }, + /* regression: the array-VARIABLE form already accepted (arr carries a + * clean N_TNAME). Local scope, since the MODULE-scope variable form is + * the still-deferred #22 (no backing symbol -> link fail on BOTH + * stages). */ + { "struct_arrvar_local", + "package main;\n" + "type pt = struct { a: i64, b: i64 };\n" + "export fn main() i32 = {\n" + "\tlet arr: [2]pt = [pt{a=1i64, b=2i64}, pt{a=3i64, b=4i64}];\n" + "\tlet g: []pt = arr;\n" + "\treturn (g[0].a:i32)+(g[0].b:i32)+(g[1].a:i32)+(g[1].b:i32)+(g.len:i32)+(g.cap:i32);\n" + "};\n", + 14 }, }; /* slice-literal static init that must FAIL the build loudly (rule 7). */