Files
ww/test/wcc/data/attest_pass.ww
Hojun-Cho 90479fed68 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.
2026-06-02 18:54:35 +09:00

130 lines
3.8 KiB
Plaintext

// @test fixture: every test passes (exits without aborting).
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 = {
let a: i32 = 2;
let b: i32 = 3;
let c: i32 = a + b;
if (c != 5) {
let _: i32 = 1 / 0; // abort via div-by-zero would also work
};
};
@test fn check_match() void = {
let r: (i32 | str) = 7;
let v: i32 = match (r) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
if (v != 7) {
let _: i32 = 1 / 0;
};
};
// #227: compound assign (`-=`/`+=`) on a local field must
// load-combine-store, not drop the op. Pre-fix wwstage stored the bare
// rhs (p.x->4, p.y->5, view.len->1), so each mismatch aborts via 1/0.
@test fn check_local_field_compound() void = {
let p: point = point { x = 10i32, y = 3i32 };
p.x -= 4i32;
p.y += 5i32;
if (p.x != 6) {
let _: i32 = 1 / 0;
};
if (p.y != 8) {
let _: i32 = 1 / 0;
};
let view: str = "hello";
view.len -= 1;
let n: i32 = view.len: i32;
if (n != 4) {
let _: i32 = 1 / 0;
};
};
// #264: a match-bound binder used inside a `yield` arm of a match-AS-
// EXPRESSION. Pre-fix the wwstage checker stamped the operand in-scope
// during the N_MCASE arm walk, then RE-TYPED it out of the (popped) arm
// scope while deriving the match's yield type (exprtype N_MATCH →
// matchyieldtype). Out of scope the operand couldn't re-resolve, the
// re-derive returned nil, and the N_UN/N_BIN/N_INDEX restamp arms
// overwrote the good in-scope stamp with nil → asserttyped:un/bin/index
// aborted (cstage accepted it). Root fix: matchyieldtype now READS the
// cached operand tinfo at the post-walk call instead of re-deriving
// (mirrors cstage match_yield_type reading body->lhs->type, check.c:121),
// so no operand shape can be clobbered. These rows pin the whole operand
// class by construction: deref (*p), bin (*p+1), slice-index (p[i]), and
// deref-then-field ((*p).x). (The *[N]T ptr-to-array index variant is
// blocked separately by #278's exhaustiveness false-reject, so it uses a
// []i32 slice binder here.)
@test fn check_match_ptr_deref() void = {
let n: i32 = 42i32;
let xi: (*i32 | void) = &n;
let v: i32 = match (xi) {
case let p: *i32 => yield *p;
case void => yield -1i32;
};
if (v != 42) {
let _: i32 = 1 / 0;
};
// N_BIN operand: `yield *p + 1`.
let m: i32 = 9i32;
let xb: (*i32 | void) = &m;
let vb: i32 = match (xb) {
case let p: *i32 => yield *p + 1i32;
case void => yield -1i32;
};
if (vb != 10) {
let _: i32 = 1 / 0;
};
// N_INDEX operand: `yield p[1]` over a []i32 binder.
let arr: [3]i32 = [4i32, 5i32, 6i32];
let sl: []i32 = arr;
let xs: ([]i32 | void) = sl;
let vs: i32 = match (xs) {
case let p: []i32 => yield p[1];
case void => yield -1i32;
};
if (vs != 5) {
let _: i32 = 1 / 0;
};
let pt: point = point { x = 7i32, y = 9i32 };
let xp: (*point | void) = &pt;
let w: i32 = match (xp) {
case let p: *point => yield (*p).x;
case void => yield -1i32;
};
if (w != 7) {
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;
};
};