diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 9927ea47..cf524b94 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -6366,15 +6366,22 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * * Task #30 graduated the builtin to `([]T | nomem)`. The let * declares a bare `[]T`, so the canonical idiom wraps in `!` - * to abort on OOM; walk into the N_TRYUNW to keep the - * direct-store fast path. */ + * (abort on OOM) or `?` (propagate nomem to the enclosing + * fn's tagged return). Task #45 extends the shortcut to also + * match N_TRYPROP and emit the propret pattern. */ { Node *call = NULL; int via_tryunw = 0; + int via_tryprop = 0; if (n->rhs && n->rhs->kind == N_TRYUNW && n->rhs->lhs && n->rhs->lhs->kind == N_CALL) { call = n->rhs->lhs; via_tryunw = 1; + } else if (n->rhs && n->rhs->kind == N_TRYPROP + && n->rhs->lhs + && n->rhs->lhs->kind == N_CALL) { + call = n->rhs->lhs; + via_tryprop = 1; } if (call && lu && lu->kind == TY_SLICE && sz == 24 && call->lhs && call->lhs->kind == N_IDENT @@ -6401,6 +6408,23 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, aimm(60), areg(D_AX)); ins0(c, A_SYSCALL); label(c, ok); + } else if (via_tryprop) { + /* #45: null = nomem; propagate to the + * enclosing fn's tagged return. AX = tag + * of nomem variant in cg_ret_type; epilogue + * RETs to caller. */ + char *ok = mklabel(c, "tryprop_ok"); + ins2(c, A_CMPQ, aimm(0), areg(D_AX)); + ins1(c, A_JNE, abranch(ok)); + Type *r = cg_ret_type; + if (r && r->kind == TY_NAMED) r = r->under; + int nidx = cg_tag_for_variant(r, ty_nomem); + if (nidx < 0) nidx = 1; + ins2(c, A_MOVQ, aimm(nidx), areg(D_AX)); + ins2(c, A_MOVQ, areg(D_BP), areg(D_SP)); + ins1(c, A_POPQ, areg(D_BP)); + ins0(c, A_RET); + label(c, ok); } ins1(c, A_POPQ, areg(D_BX)); /* count */ ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0)); diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index fd7713d4..e75e05db 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -1494,6 +1494,48 @@ clet(Checker *c, Node *n) break; } } + /* #45: alloc([], n) defers element type to the let-init context + * (Hare-style). cexpr's alloc-slice branch synthesizes + * ([]u8 | nomem) with no LHS context; when the let declares []T, + * retype the inner call (and any ?/! wrapper) to ([]T | nomem) / + * []T so the assignability check below succeeds for any T. cgen + * already drives element size from declared->sub at the N_LET + * shortcut (cmd/w6c/cgen.c). */ + if (declared && declared->kind == TY_SLICE && declared->sub + && declared->sub != ty_u8 && n->rhs) { + Node *wrap = NULL; + Node *call = n->rhs; + if (call->kind == N_TRYPROP || call->kind == N_TRYUNW) { + wrap = call; + call = call->lhs; + } + if (call && call->kind == N_CALL && call->lhs + && call->lhs->kind == N_IDENT + && call->lhs->type == ty_err + && 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) { + Type *st = type_slice(c->a, declared->sub); + Type *tt = newtype(c->a, TY_TAGGED); + Tparam *vs = amalloc(c->a, sizeof *vs); + Tparam *ve = amalloc(c->a, sizeof *ve); + vs->type = st; vs->next = ve; + ve->type = ty_nomem; ve->next = NULL; + tt->params = vs; + tt->size = 32; + tt->align = 8; + call->type = tt; + if (wrap) { + wrap->type = st; + initt = st; + } else { + initt = tt; + } + } + } if (declared && initt && initt != ty_err && !has_arr_repeat && !type_assignable(declared, initt)) err(c, n->pos, "init %s not assignable to declared %s", diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index beeacc49..39dbe5f2 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = { if (n.rhs == nil) { return; }; // no init let src: *node = exprtype(c, n.rhs); if (src == nil) { return; }; // can't infer + // #45: alloc([], n) defers element type to the let-init context + // (Hare-style). exprtype's alloc-slice branch synthesizes + // ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context; + // when the let declares []T, retype src to []T / ([]T | nomem) + // so isassignable sees exact equality. cgenstmt cglet drives the + // element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this + // stays symmetric with cstage check.c clet's parallel retype. + if (n.lhs.kind == nkind.N_TSLICE) { + let wrapped: bool = false; + let inner: *node = n.rhs; + if (inner.kind == nkind.N_TRYPROP) { + wrapped = true; + inner = inner.lhs; + } else { if (inner.kind == nkind.N_TRYUNW) { + wrapped = true; + inner = inner.lhs; + }; }; + if (inner != nil && inner.kind == nkind.N_CALL) { + let callee: *node = inner.lhs; + let a0: *node = inner.list; + let a1: *node = nil; + let a2: *node = nil; + if (a0 != nil) { a1 = a0.next; }; + if (a1 != nil) { a2 = a1.next; }; + if (callee != nil + && callee.kind == nkind.N_IDENT + && streq(callee.str, "alloc") + && a0 != nil && a0.kind == nkind.N_ARRLIT + && a0.list == nil + && a1 != nil && a2 == nil) { + let shadowed: bool = false; + if (c.curmod.len > 0) { + if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) { + shadowed = true; + }; + }; + if (!shadowed) { + let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0); + sl.lhs = n.lhs.lhs; + if (wrapped) { + src = sl; + } else { + let nome: *node = mktname(c, "nomem"); + sl.next = nome; + let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0); + tt.list = sl; + src = tt; + }; + }; + }; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; @@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = { let off: i32 = localadd(c, nm, sz, tn); if (n.rhs != nil) { let rhs: *node = n.rhs; - // `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of - // cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate - // n*esz bytes via rt_alloc, exit(1) on null, then build the - // {ptr, 0, n} slice header in the let slot. The `!` wraps the - // builtin's `([]T | nomem)` return; walk into the N_TRYUNW to - // keep the direct-store fast path rather than falling through - // to cgalloc (which models scalar alloc and would land an 8B - // region and a junk slice header). + // `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45). + // Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate + // n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice + // header in the let slot. The `!`/`?` wraps the builtin's + // `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP + // to keep the direct-store fast path rather than falling + // through to cgalloc (which models scalar alloc and would + // land an 8B region and a junk slice header). `?` propagates + // nomem via AX = tag of nomem in c.fnret, then epilogue RET. { let scall: *node = nil; let viatryunw: bool = false; + let viatryprop: bool = false; if (rhs.kind == nkind.N_TRYUNW) { if (rhs.lhs != nil) { if (rhs.lhs.kind == nkind.N_CALL) { @@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = { viatryunw = true; }; }; - }; + } else { if (rhs.kind == nkind.N_TRYPROP) { + if (rhs.lhs != nil) { + if (rhs.lhs.kind == nkind.N_CALL) { + scall = rhs.lhs; + viatryprop = true; + }; + }; + }; }; let shapeok: bool = false; if (scall != nil && tn != nil && tn.kind == nkind.N_TSLICE && sz == 24) { @@ -18397,10 +18458,7 @@ fn cglet(c: *cgen, n: *node) void = { // names too — not just primitives. elemsizeofc follows // TNAME through structlookup/aliaslookup, matching the // cstage path byte-identically. A bare primsize/slotsize - // fork would silently land esz=1 on `[]point` (today - // blocked at check.c, but the defensive cgen path must - // stay byte-identical with cstage for the moment check - // relaxes). + // fork would silently land esz=1 on `[]point`. let esz: i32 = elemsizeofc(c, tn); let count: *node = scall.list.next; cgexpr(c, count); @@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tSYSCALL\n"); emitlabel(okl); }; + if (viatryprop) { + // #45: null = nomem; propagate to the + // enclosing fn's tagged return. AX = tag + // of nomem variant in c.fnret, epilogue + // RETs to caller. + let okl: str = mklabel(c, "tryprop_ok"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJNE\t"); + emitline(okl); + emitline("\n"); + let nidx: i32 = flatvariantidx(c, c.fnret, "nomem"); + if (nidx < 0) { nidx = 1; }; + emitline("\tMOVQ\t$"); + emitint(nidx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); + emitlabel(okl); + }; emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); emitoff(off: i64); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index ae9587d9..b248bd75 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -574,17 +574,19 @@ fn cglet(c: *cgen, n: *node) void = { let off: i32 = localadd(c, nm, sz, tn); if (n.rhs != nil) { let rhs: *node = n.rhs; - // `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of - // cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate - // n*esz bytes via rt_alloc, exit(1) on null, then build the - // {ptr, 0, n} slice header in the let slot. The `!` wraps the - // builtin's `([]T | nomem)` return; walk into the N_TRYUNW to - // keep the direct-store fast path rather than falling through - // to cgalloc (which models scalar alloc and would land an 8B - // region and a junk slice header). + // `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45). + // Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate + // n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice + // header in the let slot. The `!`/`?` wraps the builtin's + // `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP + // to keep the direct-store fast path rather than falling + // through to cgalloc (which models scalar alloc and would + // land an 8B region and a junk slice header). `?` propagates + // nomem via AX = tag of nomem in c.fnret, then epilogue RET. { let scall: *node = nil; let viatryunw: bool = false; + let viatryprop: bool = false; if (rhs.kind == nkind.N_TRYUNW) { if (rhs.lhs != nil) { if (rhs.lhs.kind == nkind.N_CALL) { @@ -592,7 +594,14 @@ fn cglet(c: *cgen, n: *node) void = { viatryunw = true; }; }; - }; + } else { if (rhs.kind == nkind.N_TRYPROP) { + if (rhs.lhs != nil) { + if (rhs.lhs.kind == nkind.N_CALL) { + scall = rhs.lhs; + viatryprop = true; + }; + }; + }; }; let shapeok: bool = false; if (scall != nil && tn != nil && tn.kind == nkind.N_TSLICE && sz == 24) { @@ -618,10 +627,7 @@ fn cglet(c: *cgen, n: *node) void = { // names too — not just primitives. elemsizeofc follows // TNAME through structlookup/aliaslookup, matching the // cstage path byte-identically. A bare primsize/slotsize - // fork would silently land esz=1 on `[]point` (today - // blocked at check.c, but the defensive cgen path must - // stay byte-identical with cstage for the moment check - // relaxes). + // fork would silently land esz=1 on `[]point`. let esz: i32 = elemsizeofc(c, tn); let count: *node = scall.list.next; cgexpr(c, count); @@ -645,6 +651,24 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tSYSCALL\n"); emitlabel(okl); }; + if (viatryprop) { + // #45: null = nomem; propagate to the + // enclosing fn's tagged return. AX = tag + // of nomem variant in c.fnret, epilogue + // RETs to caller. + let okl: str = mklabel(c, "tryprop_ok"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJNE\t"); + emitline(okl); + emitline("\n"); + let nidx: i32 = flatvariantidx(c, c.fnret, "nomem"); + if (nidx < 0) { nidx = 1; }; + emitline("\tMOVQ\t$"); + emitint(nidx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); + emitlabel(okl); + }; emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); emitoff(off: i64); diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 34876d13..39f36a5b 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -975,6 +975,58 @@ fn checkletassign(c: *checker, n: *node) void = { if (n.rhs == nil) { return; }; // no init let src: *node = exprtype(c, n.rhs); if (src == nil) { return; }; // can't infer + // #45: alloc([], n) defers element type to the let-init context + // (Hare-style). exprtype's alloc-slice branch synthesizes + // ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context; + // when the let declares []T, retype src to []T / ([]T | nomem) + // so isassignable sees exact equality. cgenstmt cglet drives the + // element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this + // stays symmetric with cstage check.c clet's parallel retype. + if (n.lhs.kind == nkind.N_TSLICE) { + let wrapped: bool = false; + let inner: *node = n.rhs; + if (inner.kind == nkind.N_TRYPROP) { + wrapped = true; + inner = inner.lhs; + } else { if (inner.kind == nkind.N_TRYUNW) { + wrapped = true; + inner = inner.lhs; + }; }; + if (inner != nil && inner.kind == nkind.N_CALL) { + let callee: *node = inner.lhs; + let a0: *node = inner.list; + let a1: *node = nil; + let a2: *node = nil; + if (a0 != nil) { a1 = a0.next; }; + if (a1 != nil) { a2 = a1.next; }; + if (callee != nil + && callee.kind == nkind.N_IDENT + && streq(callee.str, "alloc") + && a0 != nil && a0.kind == nkind.N_ARRLIT + && a0.list == nil + && a1 != nil && a2 == nil) { + let shadowed: bool = false; + if (c.curmod.len > 0) { + if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) { + shadowed = true; + }; + }; + if (!shadowed) { + let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0); + sl.lhs = n.lhs.lhs; + if (wrapped) { + src = sl; + } else { + let nome: *node = mktname(c, "nomem"); + sl.next = nome; + let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0); + tt.list = sl; + src = tt; + }; + }; + }; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1b67d579..e5994e5c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = { if (n.rhs == nil) { return; }; // no init let src: *node = exprtype(c, n.rhs); if (src == nil) { return; }; // can't infer + // #45: alloc([], n) defers element type to the let-init context + // (Hare-style). exprtype's alloc-slice branch synthesizes + // ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context; + // when the let declares []T, retype src to []T / ([]T | nomem) + // so isassignable sees exact equality. cgenstmt cglet drives the + // element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this + // stays symmetric with cstage check.c clet's parallel retype. + if (n.lhs.kind == nkind.N_TSLICE) { + let wrapped: bool = false; + let inner: *node = n.rhs; + if (inner.kind == nkind.N_TRYPROP) { + wrapped = true; + inner = inner.lhs; + } else { if (inner.kind == nkind.N_TRYUNW) { + wrapped = true; + inner = inner.lhs; + }; }; + if (inner != nil && inner.kind == nkind.N_CALL) { + let callee: *node = inner.lhs; + let a0: *node = inner.list; + let a1: *node = nil; + let a2: *node = nil; + if (a0 != nil) { a1 = a0.next; }; + if (a1 != nil) { a2 = a1.next; }; + if (callee != nil + && callee.kind == nkind.N_IDENT + && streq(callee.str, "alloc") + && a0 != nil && a0.kind == nkind.N_ARRLIT + && a0.list == nil + && a1 != nil && a2 == nil) { + let shadowed: bool = false; + if (c.curmod.len > 0) { + if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) { + shadowed = true; + }; + }; + if (!shadowed) { + let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0); + sl.lhs = n.lhs.lhs; + if (wrapped) { + src = sl; + } else { + let nome: *node = mktname(c, "nomem"); + sl.next = nome; + let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0); + tt.list = sl; + src = tt; + }; + }; + }; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; @@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = { let off: i32 = localadd(c, nm, sz, tn); if (n.rhs != nil) { let rhs: *node = n.rhs; - // `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of - // cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate - // n*esz bytes via rt_alloc, exit(1) on null, then build the - // {ptr, 0, n} slice header in the let slot. The `!` wraps the - // builtin's `([]T | nomem)` return; walk into the N_TRYUNW to - // keep the direct-store fast path rather than falling through - // to cgalloc (which models scalar alloc and would land an 8B - // region and a junk slice header). + // `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45). + // Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate + // n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice + // header in the let slot. The `!`/`?` wraps the builtin's + // `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP + // to keep the direct-store fast path rather than falling + // through to cgalloc (which models scalar alloc and would + // land an 8B region and a junk slice header). `?` propagates + // nomem via AX = tag of nomem in c.fnret, then epilogue RET. { let scall: *node = nil; let viatryunw: bool = false; + let viatryprop: bool = false; if (rhs.kind == nkind.N_TRYUNW) { if (rhs.lhs != nil) { if (rhs.lhs.kind == nkind.N_CALL) { @@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = { viatryunw = true; }; }; - }; + } else { if (rhs.kind == nkind.N_TRYPROP) { + if (rhs.lhs != nil) { + if (rhs.lhs.kind == nkind.N_CALL) { + scall = rhs.lhs; + viatryprop = true; + }; + }; + }; }; let shapeok: bool = false; if (scall != nil && tn != nil && tn.kind == nkind.N_TSLICE && sz == 24) { @@ -18397,10 +18458,7 @@ fn cglet(c: *cgen, n: *node) void = { // names too — not just primitives. elemsizeofc follows // TNAME through structlookup/aliaslookup, matching the // cstage path byte-identically. A bare primsize/slotsize - // fork would silently land esz=1 on `[]point` (today - // blocked at check.c, but the defensive cgen path must - // stay byte-identical with cstage for the moment check - // relaxes). + // fork would silently land esz=1 on `[]point`. let esz: i32 = elemsizeofc(c, tn); let count: *node = scall.list.next; cgexpr(c, count); @@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = { emitline("\tSYSCALL\n"); emitlabel(okl); }; + if (viatryprop) { + // #45: null = nomem; propagate to the + // enclosing fn's tagged return. AX = tag + // of nomem variant in c.fnret, epilogue + // RETs to caller. + let okl: str = mklabel(c, "tryprop_ok"); + emitline("\tCMPQ\t$0, AX\n"); + emitline("\tJNE\t"); + emitline(okl); + emitline("\n"); + let nidx: i32 = flatvariantidx(c, c.fnret, "nomem"); + if (nidx < 0) { nidx = 1; }; + emitline("\tMOVQ\t$"); + emitint(nidx: i64); + emitline(", AX\n"); + emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n"); + emitlabel(okl); + }; emitline("\tPOPQ\tBX\n"); emitline("\tMOVQ\tAX, "); emitoff(off: i64); diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 23e2447e..e9fdfe25 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -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 diff --git a/test/wcc/950_selfcheck.c b/test/wcc/950_selfcheck.c index b664acdb..fef3cb52 100644 --- a/test/wcc/950_selfcheck.c +++ b/test/wcc/950_selfcheck.c @@ -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 diff --git a/test/wcc/994_w6c_ww.c b/test/wcc/994_w6c_ww.c index a2fa5faa..ee82ecb5 100644 --- a/test/wcc/994_w6c_ww.c +++ b/test/wcc/994_w6c_ww.c @@ -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 }, };