w6c+wwstage: reject untyped empty-[] alloc — require context, loud cannot-infer (#3 B', subsumes #5)

An empty `[]` carries no element type; ww gets it only from a let
annotation (the #45 retype). Both stages used to silently default the
element to u8, and in value-form positions (return / call-arg) the
lowering miscompiled — malloc(8) ignoring n, a 16B *u8|nomem where a 24B
slice was expected (#5). Now every empty alloc that isn't a
let-annotated binding fails to infer with a loud error, aligning ww DOWN
to harec (ref/harec/src/check.c:1801-1802).

Mechanism: clet / checkletassign flags the single alloc call node that a
`let x: []T =` rescues (save/restore around the init walk); the alloc
branch errors on any empty alloc that isn't that node. The #45 wide-T
retype path is kept. wwstage needs an extra not-yet-stamped guard because
resolvewalk re-types value nodes context-free after checkletassign.

Tests: negative cstage-driver 729 (table-driven: bare-let, return,
call-arg, assignment) + positive @test in attest_pass.ww exercising the
u8 and the wide-i32 (#45) paths at runtime. Both stages reject
symmetrically; byte-id verified on []u8 and []i32.
This commit is contained in:
2026-06-02 18:54:35 +09:00
parent bec1e7d6b0
commit 90479fed68
8 changed files with 440 additions and 0 deletions

View File

@@ -1450,6 +1450,22 @@ cexpr(Checker *c, Node *n)
!(c->cur_mod &&
scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) {
(void)cexpr(c, n->list->next);
/* B' (#3): an empty `[]` carries no element type. ww
* gets that type only from a let annotation (the
* #45 retype below). Any other empty alloc — return,
* call-arg, bare `let b = alloc([],n)` — has no hint,
* so refuse to guess instead of defaulting to u8 (was
* a silent u8-default + #5 value-form miscompile).
* Aligns ww DOWN to harec, which errors the same way:
* ref/harec/src/check.c:1801-1802. */
if (n != c->alloc_octx) {
err(c, n->pos, "cannot infer slice element "
"type for alloc([], n) without a type "
"hint; annotate the binding, e.g. "
"`let x: []T = alloc([], n)`");
n->lhs->type = ty_err;
return n->type = ty_err;
}
Type *st = type_slice(c->a, ty_u8);
/* Task #30 — slice form graduates the same way:
* `alloc([], n)` now returns `([]T | nomem)`. Slot is
@@ -1862,7 +1878,28 @@ clet(Checker *c, Node *n)
{
Type *declared = n->lhs ? resolve_type(c, n->lhs) : NULL;
Type *initt = NULL;
/* B' (#3): a `let x: []T = alloc([], n)` is the one context that
* lets the empty alloc infer its element type (the #45 retype runs
* AFTER cexpr, so flag the exact call node up front; cexpr errors on
* any empty alloc that isn't this one). Peel the same ?/! wrapper
* #45 peels so the flagged node matches. */
Node *octx = NULL;
if (declared && declared->kind == TY_SLICE && n->rhs) {
Node *call = n->rhs;
if (call->kind == N_TRYPROP || call->kind == N_TRYUNW)
call = call->lhs;
if (call && call->kind == N_CALL && call->lhs
&& call->lhs->kind == N_IDENT && call->lhs->str
&& strcmp(call->lhs->str, "alloc") == 0
&& call->list && call->list->kind == N_ARRLIT
&& call->list->list == NULL
&& call->list->next && call->list->next->next == NULL)
octx = call;
}
Node *saved_octx = c->alloc_octx;
c->alloc_octx = octx;
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 &&

View File

@@ -547,6 +547,11 @@ struct Checker {
* would shadow an imported module bareword. */
int loops; /* nesting count for break/continue */
int errs;
Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node
* that has let-declared slice context this walk;
* any OTHER empty alloc has no element-type hint
* and must fail to infer (harec check.c:1801).
* Set by clet around its cexpr, NULL elsewhere. */
};
void check_init(Checker*, Arena*);