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).
This commit is contained in:
2026-06-03 22:51:24 +09:00
parent 8dda8ea76c
commit 1e081e28c6
4 changed files with 116 additions and 3 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 <stdio.h>
#include <stdlib.h>
@@ -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). */