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:
6
Makefile
6
Makefile
@@ -256,6 +256,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_cgreturn_variant_zero \
|
||||
$(BIN)/test_arrlit_str_full \
|
||||
$(BIN)/test_redecl \
|
||||
$(BIN)/test_empty_alloc_infer \
|
||||
$(BIN)/test_struct_field_index \
|
||||
$(BIN)/test_tagged_return_scratch \
|
||||
$(BIN)/test_tagged_widen_f64 \
|
||||
@@ -979,6 +980,11 @@ $(BIN)/test_redecl: test/wcc/712_redecl.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_empty_alloc_infer: test/wcc/729_empty_alloc_infer.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_struct_field_index: test/wcc/713_struct_field_index.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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*);
|
||||
|
||||
@@ -10444,6 +10444,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -12810,6 +12816,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -14322,7 +14345,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
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.
|
||||
// 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
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
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) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -14779,6 +14835,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -2405,6 +2411,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -3917,7 +3940,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
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.
|
||||
// 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
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
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) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -4374,6 +4430,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
@@ -10444,6 +10444,12 @@ type checker = struct {
|
||||
file: *node, // N_FILE root; used by checkmoduleshadow
|
||||
// to consult the declaring source's own
|
||||
// `use` directives.
|
||||
allococtx: *node, // #3/B': the one empty alloc([], n) call node
|
||||
// with let-declared slice context this walk;
|
||||
// any other empty alloc has no element hint
|
||||
// and must fail to infer (harec
|
||||
// check.c:1801). Set by checkletassign
|
||||
// around its exprtype, nil elsewhere.
|
||||
};
|
||||
|
||||
// seedprimitives — install the built-in type names so `i32`, `str`,
|
||||
@@ -12810,6 +12816,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (e.list.list == nil) {
|
||||
if (e.list.next != nil) {
|
||||
if (e.list.next.next == nil) {
|
||||
// B' (#3): an empty `[]` has no element type;
|
||||
// ww gets it only from a let annotation (the
|
||||
// #45 retype). Any other empty alloc has no
|
||||
// hint, so refuse to guess rather than default
|
||||
// to u8 (was a silent u8-default + #5 value-form
|
||||
// miscompile). Align DOWN to harec, which errors
|
||||
// the same way: ref/harec/src/check.c:1801-1802.
|
||||
// The e.type_ != nil guard is the wwstage-only half:
|
||||
// resolvewalk re-types every value node context-free
|
||||
// (L648) AFTER checkletassign already rescued+stamped
|
||||
// this node, so a stamped node is a rescued one — do
|
||||
// not re-error it. cstage cexpr is single-visit (clet
|
||||
// only) so it needs only the allococtx check.
|
||||
if (e != c.allococtx && e.type_ == nil) {
|
||||
deffolderr(c, e, "cannot infer slice element type for alloc([], n) without a type hint; annotate the binding, e.g. let x: []T = alloc([], n)");
|
||||
return nil;
|
||||
};
|
||||
let sl: *node = newnode(nkind.N_TSLICE, "", 0, 0);
|
||||
sl.lhs = mktname(c, "u8");
|
||||
let nome: *node = mktname(c, "nomem");
|
||||
@@ -14322,7 +14345,40 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
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.
|
||||
// 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
|
||||
// exprtype, so flag the exact call node up front; exprtype errors on
|
||||
// any empty alloc that isn't this one). Peel the same ?/! wrapper #45
|
||||
// peels so the flagged node matches.
|
||||
let octx: *node = nil;
|
||||
if (n.lhs != nil && n.lhs.kind == nkind.N_TSLICE) {
|
||||
let inner: *node = n.rhs;
|
||||
if (inner.kind == nkind.N_TRYPROP) {
|
||||
inner = inner.lhs;
|
||||
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||
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) {
|
||||
octx = inner;
|
||||
};
|
||||
};
|
||||
};
|
||||
let savedoctx: *node = c.allococtx;
|
||||
c.allococtx = octx;
|
||||
let src: *node = exprtype(c, n.rhs, nil);
|
||||
c.allococtx = savedoctx;
|
||||
// Inferred binding (`let r = expr;`, no type annotation). Mirror
|
||||
// cstage cmd/wcc/check.c:1477 clet `if (t == NULL && initt) t =
|
||||
// type_default(initt);` and ref/harec/src/check.c:1422
|
||||
@@ -14779,6 +14835,7 @@ export fn checkinit(c: *checker, tc: *tctx) void = {
|
||||
let empty: str;
|
||||
c.curmod = empty;
|
||||
c.file = nil;
|
||||
c.allococtx = nil;
|
||||
seedprimitives(c);
|
||||
};
|
||||
|
||||
|
||||
198
test/wcc/729_empty_alloc_infer.c
Normal file
198
test/wcc/729_empty_alloc_infer.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 729_empty_alloc_infer — check: an untyped empty `alloc([], n)` must
|
||||
* loudly fail to infer its slice element type instead of silently
|
||||
* defaulting to []u8 (task #3 / B', subsumes #5).
|
||||
*
|
||||
* Pre-fix: cmd/wcc/check.c's alloc-slice branch pinned the element type
|
||||
* to u8 with no context. `let b = alloc([], n)!` (no annotation) became
|
||||
* []u8, and the value-form lowerings `return alloc([], n)!` /
|
||||
* `f(alloc([], n))` SILENTLY MISCOMPILED (malloc(8) ignoring n; a 16B
|
||||
* *u8|nomem where a 24B slice was expected) — that was bug #5.
|
||||
*
|
||||
* Post-fix: ww aligns DOWN to harec, which refuses to guess —
|
||||
* "Cannot infer array type from context" (ref/harec/src/check.c:1801).
|
||||
* The ONLY context that supplies the element type is the let annotation
|
||||
* (the #45 retype), so an annotated alloc of ANY element type still
|
||||
* compiles; every other empty alloc errors at check time.
|
||||
*
|
||||
* Cstage-driver negative test, same shape as 712_redecl. The wwstage
|
||||
* twin (selfhost/cmd/wcc/check.ww, exprtype alloc-slice branch +
|
||||
* checkletassign context-flag) rejects symmetrically; it is validated by
|
||||
* the 990-997 byte-id gates rebuilding the *_ww tools from check.ww. The
|
||||
* positive @test (annotated []u8 / []i32 alloc) rides attest_pass.ww
|
||||
* (910/997, dual-stage).
|
||||
*
|
||||
* row | kind | what it pins
|
||||
* ---------------------+-------------+------------------------------
|
||||
* neg_bare_let | build fails | no-annotation u8 default gone
|
||||
* neg_return | build fails | #5 value-form return
|
||||
* neg_call_arg | build fails | #5 value-form call-arg
|
||||
* neg_assign | build fails | re-bind has no annotation hint
|
||||
* pos_annotated_u8 | exit=16 | let []u8 still infers
|
||||
* pos_annotated_wide | exit=5 | let []i32 (#45 retype) infers
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* kind == 0: negative — build must fail (any nonzero exit).
|
||||
* kind == 1: positive — build must succeed AND binary exits with `want`.
|
||||
*/
|
||||
struct row { const char *label; int kind; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* neg: bare let, no annotation — was a silent []u8 default. */
|
||||
{ "neg_bare_let", 0,
|
||||
"fn main() i32 = {\n"
|
||||
" let b = alloc([], 8u64)!;\n"
|
||||
" return b.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* neg: value-form return — #5 silent miscompile. */
|
||||
{ "neg_return", 0,
|
||||
"fn mk() []u8 = { return alloc([], 8u64)!; };\n"
|
||||
"fn main() i32 = { let b: []u8 = mk(); return b.len: i32; };\n",
|
||||
0 },
|
||||
|
||||
/* neg: value-form call-arg — #5 silent miscompile. */
|
||||
{ "neg_call_arg", 0,
|
||||
"fn g(x: []u8) i32 = { return x.len: i32; };\n"
|
||||
"fn main() i32 = { return g(alloc([], 8u64)!); };\n",
|
||||
0 },
|
||||
|
||||
/* neg: assignment target supplies NO context (only the let
|
||||
* annotation does; #45). A re-bind `x = alloc([], n)` must error
|
||||
* too — top-down hint threading to assign is A', out of scope. */
|
||||
{ "neg_assign", 0,
|
||||
"fn main() i32 = {\n"
|
||||
" let x: []u8 = alloc([], 4u64)!;\n"
|
||||
" x = alloc([], 8u64)!;\n"
|
||||
" return x.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* pos: let annotation supplies the element type (u8). alloc([], n)
|
||||
* is Hare's len=0 / cap=n empty slice, so write into the cap-backed
|
||||
* memory and read back — also proves the u8 element stride. */
|
||||
{ "pos_annotated_u8", 1,
|
||||
"import rt;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let b: []u8 = alloc([], 8u64)!;\n"
|
||||
" b[0] = 7u8;\n"
|
||||
" b[3] = 9u8;\n"
|
||||
" return (b[0]: i32) + (b[3]: i32);\n"
|
||||
"};\n",
|
||||
16 },
|
||||
|
||||
/* pos: let annotation with a wide element — the #45 retype. The i32
|
||||
* stride (4B) must drive indexing, not the u8 default. */
|
||||
{ "pos_annotated_wide", 1,
|
||||
"import rt;\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let w: []i32 = alloc([], 4u64)!;\n"
|
||||
" w[2] = 5i32;\n"
|
||||
" return w[2];\n"
|
||||
"};\n",
|
||||
5 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[128], tmpdir[128], cmd[2048];
|
||||
snprintf(src, sizeof src, "/tmp/wcealloc_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wcealloc_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[256];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
char combined[256];
|
||||
snprintf(combined, sizeof combined, "/tmp/wcealloc_%d_%d.combined.ww",
|
||||
getpid(), i);
|
||||
|
||||
if (r->kind == 0) {
|
||||
/* Negative — build must fail. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build %s >/dev/null 2>&1", tmpdir, driver, src);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr,
|
||||
"ealloc[%s]: build unexpectedly succeeded\n",
|
||||
r->label);
|
||||
unlink(outbin);
|
||||
}
|
||||
unlink(src);
|
||||
unlink(combined);
|
||||
rmdir(tmpdir);
|
||||
return rc == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Positive — build (with rt linked via `ww run`) then check exit. */
|
||||
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", driver, src);
|
||||
int got = runwait(cmd);
|
||||
unlink(src);
|
||||
unlink(outbin);
|
||||
unlink(combined);
|
||||
rmdir(tmpdir);
|
||||
if (got != r->want) {
|
||||
fprintf(stderr, "ealloc[%s]: exit=%d want=%d\n",
|
||||
r->label, got, r->want);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int fail = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (run_row(cdrv, &rows[i], i) != 0) fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "ealloc: %d/%d row(s) failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("ealloc: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
package data;
|
||||
|
||||
import rt; // #3/B': check_empty_alloc_annotated calls alloc → rt_malloc.
|
||||
|
||||
type point = struct { x: i32, y: i32 };
|
||||
|
||||
@test fn check_add() void = {
|
||||
@@ -104,3 +106,24 @@ type point = struct { x: i32, y: i32 };
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
};
|
||||
|
||||
// #3/B': the let-annotation context path keeps `alloc([], n)` inferring
|
||||
// its element type for ANY T (u8 default + non-u8 #45 retype). The bare /
|
||||
// return / arg empty allocs that lost the silent u8 default are covered by
|
||||
// the negative test (test/wcc/729). Here we pin that annotated allocs of
|
||||
// both an 8-bit and a wide element still compile and index correctly.
|
||||
// alloc([], n) is Hare's len=0 / cap=n empty slice, so we write into the
|
||||
// cap-backed memory and read it back (ref/hare expects len 0, not n).
|
||||
@test fn check_empty_alloc_annotated() void = {
|
||||
let b: []u8 = alloc([], 8u64)!;
|
||||
b[0] = 7u8;
|
||||
b[3] = 9u8;
|
||||
if (b[0] != 7 || b[3] != 9) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
let w: []i32 = alloc([], 4u64)!;
|
||||
w[2] = 5i32;
|
||||
if (w[2] != 5) {
|
||||
let _: i32 = 1 / 0;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user