w6c+wcc/check: infer [_]T array length from initializer element count (fix #7)
`[_]T = [...]` (canonical Hare array-length inference) silently miscompiled to a zero-length array: the parser already left the array type's length child nil as the infer sentinel — distinct from an explicit [N] — but neither checker stamped the real count, so `len(x)` returned 0 with no diagnostic (rule-7 silent miscompile). Module-level was worse on wwstage, where `x.len` on ANY global array (even an explicit [N]) fell to the SB fallback and mis-emitted `MOVQ len(SB), AX` (linker: undefined reference to len). The length lives in the stamped TYPE and cgen already keys stride / length / data-emission off it, so stamping the inferred count at the one checker inference point closes it permanently (rob's #7 ruling): - check.c clet + module-level N_LET pass-2: count the initializer's elements and patch the array type's length (the Sym too, so a later x.len reads the inferred alen). No-init / non-array init can't infer -> loud error, never a silent zero-length array. - check.ww inferarraylen: the wwstage twin — stamp a synthesized N_INTLIT length child before resolvewalk caches the array tinfo; same loud-error rule. Idempotent for the module-level double-call. - cgenexpr.ww cgdot: the missing wwstage arm for a top-level [N]T global's .len / .ptr (cstage cgen.c:8011 already had it). - cgenutil.ww letslotsize: drop the now-redundant [_] slot-size intercept — a workaround for this very bug; the stamped length flows through the general slotsize path (rule 7). Both stages converge byte-identical; new table-driven test 684 covers [_]int/[_]str/[_]u8 local + module-level, len + element read-back, dual-stage runtime + asm byte-id, plus three negative no-infer rows.
This commit is contained in:
@@ -3946,8 +3946,48 @@ fn checkassign(c: *checker, n: *node) void = {
|
||||
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
|
||||
};
|
||||
|
||||
// inferarraylen — `let xs: [_]T = arrlit;` length inference (#7). The
|
||||
// parser leaves a `[_]` array's length child nil as the infer sentinel
|
||||
// (parse.ww, mirror cstage parse.c:186). Count the array-literal's
|
||||
// elements (skipping the `...` repeat marker, same walk as the N_ARRLIT
|
||||
// exprtype at L2905) and stamp a synthesized N_INTLIT length node so
|
||||
// tinfofornode / cgen / `.len` all read the real count — the wwstage
|
||||
// analogue of cstage clet's `declared = type_array(.., iu->alen)` patch.
|
||||
// A `[_]T` with no array-literal initialiser can't infer: loud error,
|
||||
// never a silent zero-length array (rule 7). Idempotent (skips once the
|
||||
// length child is set), so the module-level double-call (checkfile's
|
||||
// pre-resolvewalk call + checkletassign here) raises at most one error.
|
||||
fn inferarraylen(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
if (n.lhs == nil) { return; };
|
||||
if (n.lhs.kind != nkind.N_TARRAY) { return; };
|
||||
if (n.lhs.rhs != nil) { return; }; // explicit [N] or already inferred
|
||||
if (n.rhs == nil || n.rhs.kind != nkind.N_ARRLIT) {
|
||||
cerr("error: [_]T needs an array-literal initialiser\n");
|
||||
c.errs += 1;
|
||||
let z: *node = newnode(nkind.N_INTLIT, "", 0, 0);
|
||||
z.uval = 0u64;
|
||||
n.lhs.rhs = z; // sentinel: idempotent, error already raised
|
||||
return;
|
||||
};
|
||||
let cnt: u64 = 0u64;
|
||||
let it: *node = n.rhs.list;
|
||||
for (it != nil) {
|
||||
let skip: bool = false;
|
||||
if (it.kind == nkind.N_FIELD) {
|
||||
if (streq(it.str, "...")) { skip = true; };
|
||||
};
|
||||
if (!skip) { cnt += 1u64; };
|
||||
it = it.next;
|
||||
};
|
||||
let cn: *node = newnode(nkind.N_INTLIT, "", 0, 0);
|
||||
cn.uval = cnt;
|
||||
n.lhs.rhs = cn;
|
||||
};
|
||||
|
||||
fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n == nil) { return; };
|
||||
inferarraylen(c, n); // #7: must run before the n.rhs==nil bail
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
// hint = nil for A.6.0; A.6.1 will pass n.lhs once STRUCTLIT/ARRLIT
|
||||
// arms consume it. Plumbing-only at this point.
|
||||
@@ -4503,6 +4543,12 @@ export fn checkfile(c: *checker, file: *node) void = {
|
||||
case nkind.N_TYPEDECL:
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
case nkind.N_LET:
|
||||
// #7: a module-level `let xs: [_]T = arrlit;` must infer
|
||||
// its length BEFORE resolvewalk stamps d.lhs's tinfo —
|
||||
// otherwise the array tinfo caches the alen=0 sentinel and
|
||||
// the patched length child never reaches the size/data
|
||||
// reads. Idempotent with the checkletassign call below.
|
||||
inferarraylen(c, d);
|
||||
if (d.lhs != nil) { resolvewalk(c, d.lhs); };
|
||||
if (d.rhs != nil) { resolvewalk(c, d.rhs); };
|
||||
// #130: top-level let assignability — the subtree
|
||||
|
||||
Reference in New Issue
Block a user