cmd+selfhost+test: relax alloc-slice element-type pin via LHS retype

`alloc([], n)` synthesizes ([]u8 | nomem) at expression level — that's
fine, since the slice form only legitimately appears in let-init
position where the LHS carries the real element type. In clet, after
type-checking the rhs, peel any N_TRYPROP/N_TRYUNW wrapper, match the
alloc-slice AST shape with the same-module shadow gate (from #23),
and retype the call's tagged return to ([]T | nomem) where T is the
declared LHS element. Then assignability sees []T vs []T and accepts.

Cgen N_LET shortcut gains a viatryprop arm next to the existing
viatryunw — on rt_alloc returning null, emits the tagged-return
nomem propagation (MOVQ $nidx, AX; epilogue) instead of exit(1).
nidx comes from cg_tag_for_variant on the enclosing fn's return type,
matching the existing TRYPROP propret path.

Wwstage mirrors all four hunks (check.ww + cgenstmt.ww). Promotes the
previously-silent conf=false skip into a confident accept.

Unblocks #6 (dupall) and lays the path for #4/#7. Byte-identity
holds modulo the pre-existing #44 alloc/rt_alloc symbol divergence.
This commit is contained in:
2026-05-20 01:09:16 +09:00
parent 30a0856fe5
commit 4d4ad36b70
9 changed files with 395 additions and 41 deletions

View File

@@ -307,6 +307,34 @@ static const struct row rows[] = {
" append(s, 72u8, 105u8);\n"
" return s.cap;\n"
"};", 16 },
/* #45: alloc([], n) now defers element type to the let-init LHS.
* `[]rune` is 4B-per-element; the cgen shortcut scales count by
* size(T). Returns s.cap = 8. */
{ "package main;\n"
"import os;\n"
"fn main() i32 = {\n"
" let s: []rune = alloc([], 8)!;\n"
" return s.cap;\n"
"};", 8 },
/* #45: same as above for `[]str` (16B-per-element). */
{ "package main;\n"
"import os;\n"
"fn main() i32 = {\n"
" let s: []str = alloc([], 4)!;\n"
" return s.cap;\n"
"};", 4 },
/* #45: `?` form. doit propagates nomem to its (i32 | nomem)
* return; the alloc-slice shortcut emits MOVQ $nomem_tag, AX +
* propret on null. doit returns 12 on success; main unwraps. */
{ "package main;\n"
"import os;\n"
"fn doit() (i32 | nomem) = {\n"
" let s: []str = alloc([], 12)?;\n"
" return s.cap: i32;\n"
"};\n"
"fn main() i32 = {\n"
" return doit()!;\n"
"};", 12 },
/* alloc-builtin shadow (task #23): a non-main package declares
* `fn alloc(n: i64) i64` and calls it bare. The same-module gate
* must suppress the builtin and dispatch to the user fn so the

View File

@@ -144,6 +144,20 @@ static const struct row rows[] = {
" let s: []u8 = alloc([], 16)!;\n"
"};\n",
NULL },
/* #45: alloc([], n) defers element type to LHS context — `[]str`
* with `!` must be accepted (cstage clet retypes, wwstage
* checkletassign mirror). Pre-#45 this errored with
* "init []u8 not assignable to declared []str" on cstage. */
{ "fn caller() void = {\n"
" let s: []str = alloc([], 4)!;\n"
"};\n",
NULL },
/* #45: same idiom in `?` form inside a (T | nomem) fn. */
{ "fn caller() (i32 | nomem) = {\n"
" let s: []str = alloc([], 4)?;\n"
" return s.cap: i32;\n"
"};\n",
NULL },
};
int

View File

@@ -199,6 +199,24 @@ main(void)
" for (let (k, v) .. s) { total += k + v; };\n"
" return total: i32;\n"
"};" },
/* #45: alloc-slice `!` form, []T element ≠ u8. */
{ "alloc_slice_rune_unw",
"package main;\n"
"import os;\n"
"fn main() i32 = {\n"
" let s: []rune = alloc([], 8)!;\n"
" return s.cap;\n"
"};" },
/* #45: alloc-slice `?` form propagates nomem via the
* enclosing fn's tagged return. */
{ "alloc_slice_str_prop",
"package main;\n"
"import os;\n"
"fn doit() (i32 | nomem) = {\n"
" let s: []str = alloc([], 4)?;\n"
" return s.cap: i32;\n"
"};\n"
"fn main() i32 = { return doit()!; };" },
{ NULL, NULL },
};