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:
2026-06-03 18:44:39 +09:00
parent 63cb39e45b
commit 7ca32432b1
8 changed files with 653 additions and 136 deletions

View File

@@ -1901,10 +1901,14 @@ clet(Checker *c, Node *n)
if (n->rhs) initt = cexpr(c, n->rhs);
c->alloc_octx = saved_octx;
/* `let xs: [_]T = arrlit;` — fill in the inferred length from the
* initialiser. `resolve_type` left alen=0 as a sentinel. */
if (declared && declared->kind == TY_ARRAY && declared->alen == 0 &&
initt) {
Type *iu = (initt->kind == TY_NAMED) ? initt->under : initt;
* initialiser. `resolve_type` left alen=0 as a sentinel. A `[_]T`
* with no array-literal initialiser (no init at all, or a non-array
* init) can't infer its length — that is a loud error, never a
* silent zero-length array (rule 7, #7). */
if (declared && declared->kind == TY_ARRAY && declared->alen == 0) {
Type *iu = initt
? ((initt->kind == TY_NAMED) ? initt->under : initt)
: NULL;
if (iu && iu->kind == TY_ARRAY)
declared = type_array(c->a, declared->sub, iu->alen);
else
@@ -2545,12 +2549,46 @@ check_file(Checker *c, Node *file)
case N_LET: {
if (d->rhs) {
Type *rt = cexpr(c, d->rhs);
/* `let xs: [_]T = arrlit;` at module level — infer the
* length from the initialiser, the same patch clet
* applies for a local let (#7). pass-1.5 resolve_type
* left alen=0 as the sentinel; patching d->type feeds
* cgen's letvars registration (lv->type = d->type),
* which both lays the full-length DATA row and reads
* the right `.len`. */
if (d->type && d->type->kind == TY_ARRAY
&& d->type->alen == 0) {
Type *iu = rt
? ((rt->kind == TY_NAMED) ? rt->under : rt)
: NULL;
if (iu && iu->kind == TY_ARRAY) {
d->type = type_array(c->a,
d->type->sub, iu->alen);
/* The Sym installed in pass-1.5 still
* carries the alen=0 sentinel; a later
* `x.len` resolves `x` through the Sym
* (its type stamps n->lhs->type, which
* cgen reads as u->alen). Re-point it at
* the inferred-length type too. */
Sym *s = scope_lookup_local(c->cur,
d->str);
if (s) s->type = d->type;
} else
err(c, d->pos, "[_]T needs an "
"array-literal initialiser");
}
if (d->type == NULL) d->type = type_default(rt);
if (d->type && rt != ty_err && d->type != ty_err
&& !type_assignable(d->type, rt)
&& !arrlit_init_fits(c, d->type, d->rhs))
err(c, d->pos, "let %s init not assignable",
d->str);
} else if (d->type && d->type->kind == TY_ARRAY
&& d->type->alen == 0) {
/* `let x: [_]T;` — no initialiser, length can't be
* inferred (rule 7, #7). */
err(c, d->pos, "[_]T needs an array-literal "
"initialiser");
}
break;
}