// opaque_assign_cast_test — runtime proof of #108 sub-fold (c): opaque as a // type-erasure sink. Two assignability rules + the reinterpret casts sort's // implementation relies on. Migrated from test/wcc/962_opaque_assign_cast_run.c. // // rule 1 `*T -> *opaque` IMPLICIT (no cast). Any pointer is the universal // void-pointer. harec type_is_assignable, pointer arm: // ref/harec/src/types.c:1053 (`case STORAGE_OPAQUE: break;` — the // referent need not match). // rule 2 `[]T -> []opaque` IMPLICIT (no cast). Any slice is the type-erased // slice; the {ptr,len,cap} header is normal, the byte stride is // supplied at runtime (itemsz). harec slice arm: types.c:1094 // (`if (to_secondary->storage == STORAGE_OPAQUE) return true;`). // casts `[]opaque -> *u8` (slice -> byte ptr; cgexpr leaves the ptr in AX, // so the cast naturally takes .ptr) and `*opaque -> *u8` / // `*opaque -> *i32` (ptr->ptr reinterpret, a no-op). drew described // the Hare idiom as `*[*]u8`; ww has no unbounded-array `[*]`, so the // ww-faithful reinterpret target is `*u8` + uintptr stride arithmetic. // // rules 1 & 2 are CSTAGE-led; the wwstage check.ww isassignable is a // resolve-only AST approximation that returns "can't tell, stay quiet" // (confident=false) for a ptr/slice whose element it cannot match, so it // already ACCEPTS every form here. The casts are validation-free in BOTH stages // (N_CAST never checks legality). The T2 byte-id floor (LANGBYTEID) carries the // cs==ww rule-10 proof directly — opaque is inert on the selfhost corpus so the // 990-997 gates never exercise it. // // Array->[]opaque (harec's array->slice decay) is deliberately EXCLUDED: ww has // no implicit array->slice conversion for any element type (a slice is built // only via an explicit `a[0:n]`), so there is no array->slice-header cgen. // // Call results are bound to locals before each assert (the test/lang idiom), // not compared inline. The C original avoided an inline `if (f(x) != k)` shape // (arg from a prior uintptr-arith call) that a pre-#116-era cgen bug once // mis-compiled; that form now compiles correct + cs==ww on HEAD, so the binding // is style, not a live workaround. The opaque feature is exercised in full. package opaque_assign_cast_test; fn readi32(p: *opaque) i32 = { let pi: *i32 = p: *i32; return *pi; }; fn slen(o: []opaque) i32 = { return o.len: i32; }; fn first(o: []opaque) i32 = { let p: *opaque = o.ptr; let pi: *i32 = p: *i32; return *pi; }; fn elemptr(items: []opaque, i: size, itemsz: size) *opaque = { let base: *u8 = items: *u8; // []opaque -> *u8 (takes .ptr) let off: uintptr = (i * itemsz): uintptr; return ((base: uintptr) + off): *opaque; }; fn swap(items: []opaque, x: size, y: size, itemsz: size) void = { let pa: *u8 = elemptr(items, x, itemsz): *u8; let pb: *u8 = elemptr(items, y, itemsz): *u8; let k: size = 0; for (k < itemsz) { let qa: *u8 = ((pa: uintptr) + k: uintptr): *u8; let qb: *u8 = ((pb: uintptr) + k: uintptr): *u8; let t: u8 = *qa; *qa = *qb; *qb = t; k = k + 1; }; }; @test fn rule1_implicit_ptr() void = { // rule 1: `*T -> *opaque` IMPLICIT (no cast) at a let-init AND a call-arg. // Round-trip a real *i32 through *opaque and back, deref. let n: i32 = 42; let po: *opaque = &n; // let-init *i32 -> *opaque let v: i32 = readi32(po); let v2: i32 = readi32(&n); // call-arg *i32 -> *opaque assert(v == 42); assert(v2 == 42); }; @test fn rule2_implicit_slice() void = { // rule 2: `[]T -> []opaque` IMPLICIT (no cast) at a let-init AND a call-arg. // Read .len, round-trip .ptr (a *opaque) back to *i32. let a: [4]i32 = [11, 22, 33, 44]; let s: []i32 = a[0:4]; let o: []opaque = s; // let-init []i32 -> []opaque let n: i32 = slen(o); let n2: i32 = slen(s); // call-arg []i32 -> []opaque assert(n == 4); assert(n2 == 4); let f: i32 = first(s); assert(f == 11); }; @test fn sort_pattern() void = { // sort's actual usage end-to-end: a `fn(items: []opaque, itemsz: size)` // called with a []i32; inside, reinterpret the slice as a byte base // (`items: *u8`), uintptr-arith two element addresses, byte-swap them by // itemsz. Then read back through the *opaque element path and the original // []i32 view — the type erasure round-trips iff both agree. let a: [4]i32 = [10, 20, 30, 40]; let s: []i32 = a[0:4]; swap(s, 0: size, 3: size, size(i32)); // []i32 -> []opaque call-arg assert(a[0] == 40); // erased swap round-trips assert(a[3] == 10); let p0: *opaque = elemptr(s, 0: size, size(i32)); let v0: i32 = readi32(p0); assert(v0 == 40); let nn: i32 = 77; let pn: *opaque = &nn; // *i32 -> *opaque let-init let vn: i32 = readi32(pn); assert(vn == 77); let o: []opaque = s; let ol: i32 = o.len: i32; assert(ol == 4); };