test: port the xmod typecheck reject observers to ww

test/xmod/typecheck_test.ww replaces 989_callarg_typecheck.c and
989_tagged_subset_reject.c. Preserved: both -T __wwtests reservation
faces (both stages, diagnostic required), the shape-mismatch collision
(both stages), the cstage-only same-shape #37 pin, and both tagged
leaf-bridge xmod rows. Not ported, owned elsewhere per the audit: the
carriers' twelve ordinary call-arg rows (r989_callarg_* fixtures) and
the three w6c-vs-w6c_ww byte-id rows (test-data-byteid over
r989_tagsubset_{flatten_subset,slice_subset,nested_wrap}).
This commit is contained in:
2026-08-08 14:31:53 +09:00
parent cf74d59e87
commit e0afe48c21
4 changed files with 254 additions and 777 deletions

253
test/xmod/typecheck_test.ww Normal file
View File

@@ -0,0 +1,253 @@
package typecheck_test;
// Cross-module assignability/reservation reject observers. Ports of
// the retired native carriers test/wcc/989_callarg_typecheck.c and
// test/wcc/989_tagged_subset_reject.c.
//
// callarg (#24) — the twelve ordinary call-arg rows are owned by the
// r989_callarg_* corpus fixtures (the carrier header records the
// split); the unowned remainder ported here:
// wwtests_int | user `const __wwtests: int` + @test fn under
// | `ww test` -> loud reject, BOTH stages; stderr
// | must carry "__wwtests is reserved by -T" (a
// | crash without the diagnostic FAILS, #20)
// wwtests_shadow | user __wwtests whose type MATCHES run()'s param
// | ([](str,*fn() void)) slips the (a) isassignable
// | teeth; only the (b) name reservation rejects
// shape_mismatch | []int passed where io2.handle=(file|stream) is
// | wanted -> "not assignable", BOTH stages (#24-B
// | shape-matched-lenient leg)
// same_shape | mod1.stream (*wbox) into io2.take -> cstage-ONLY
// | nominal reject; the wwstage over-accept is the
// | documented #10/#66/#37 nominal residual,
// | deliberately NOT asserted (dark until #37)
//
// tagged_subset (A7) — the three asm byte-id rows are owned by
// test-data-byteid over their corpus twins
// r989_tagsubset_{flatten_subset,slice_subset,nested_wrap}; the two
// cross-module leaf-bridge rows ported here (intrinsically two-module:
// one file spells every name bare, so typeeqast streq matches and the
// leaf bridge is never exercised):
// xmod_inline_forward | e.next's inline (i64|done) spelled BARE
// | forwards into qualified (i64|e.done):
// | build+run exit 0 both stages (dropping the
// | leaf bridge regresses cs=0/ww=1)
// xmod_inline_narrow | src carries `more`, absent from dst -> build
// | fails with "not assignable" on both stages
// | (missing diagnostic = crash, fails)
//
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
// (the Make target declares both drivers) and the unlink/rmdir
// accounting (testenv.clean asserts the removal).
import os;
import os.exec;
import strings;
import testenv;
import time;
fn fail(label: str, why: str) void = {
let m: str = strings.concat("typecheck FAIL: ", label, " -- ", why,
"\n");
os.write(2, m.ptr, m.len: u64);
assert(false);
};
fn tmo() time.duration = {
return (240i64 * (time.second: i64)): time.duration;
};
// ---- -T __wwtests reservation faces ------------------------------------
fn tface(label: str, src: str) void = {
let drvs: []str = ["ww", "ww_ww"];
let i: i32 = 0;
for (i < 2) {
let td: str = testenv.fresh();
let input: str = strings.concat(td, "/input.ww");
testenv.writefile(input, src);
let av: []str = [testenv.driver(drvs[i]), "test", "-o",
strings.concat(td, "/prog"), input];
let co: testenv.commandout;
testenv.runcommand(td, td, strings.concat("t_", drvs[i]), av,
tmo(), &co);
if (co.termination == exec.termination.EXIT && co.code == 0) {
fail(label, strings.concat(drvs[i], " test accepted ",
"(expected a loud reject)"));
};
if (!testenv.has(co.stderr, "__wwtests is reserved by -T")) {
fail(label, strings.concat(drvs[i], " nonzero exit but ",
"missing reservation diagnostic (a crash, not a clean ",
"reject -- #20)"));
};
testenv.clean(td);
i += 1;
};
};
@test fn wwtests_int() void = {
tface("wwtests_int", strings.concat(
"package main;\n",
"const __wwtests: int = 99;\n",
"@test fn checkfoo() void = { return; };\n"));
};
@test fn wwtests_shadow() void = {
tface("wwtests_shadow", strings.concat(
"package main;\n",
"fn dummy() void = { return; };\n",
"const __wwtests: [](str, *fn() void) = [(\"x\", &dummy)];\n",
"@test fn checkfoo() void = { return; };\n"));
};
// ---- cross-module call-arg collision rows ------------------------------
fn io2src() str = {
return strings.concat(
"package io2;\n",
"export type vtable = struct { x: i32 };\n",
"export type stream = *vtable;\n",
"export type file = i32;\n",
"export type handle = (file | stream);\n",
"export fn take(h: handle) int = { return 7; };\n");
};
// The build must FAIL with the "not assignable" diagnostic (a crash
// or a diag-less reject also fails, #20).
fn collidereject(label: str, drv: str, withmod1: bool,
mainbody: str) void = {
let td: str = testenv.fresh();
assert(os.mkdir(strings.concat(td, "/io2"), 493) == 0);
testenv.writefile(strings.concat(td, "/io2/io2.ww"), io2src());
let av: []str = [];
append(av, testenv.driver(drv));
append(av, "build");
append(av, "-I");
append(av, strings.concat(td, "/io2"));
if (withmod1) {
assert(os.mkdir(strings.concat(td, "/mod1"), 493) == 0);
testenv.writefile(strings.concat(td, "/mod1/mod1.ww"),
strings.concat(
"package mod1;\n",
"export type wbox = struct { y: i64 };\n",
"export type stream = *wbox;\n",
"export fn mk() stream = { return nil; };\n"));
append(av, "-I");
append(av, strings.concat(td, "/mod1"));
};
testenv.writefile(strings.concat(td, "/main.ww"), mainbody);
append(av, "main.ww");
let co: testenv.commandout;
testenv.runcommand(td, td, strings.concat("b_", drv), av, tmo(), &co);
if (co.termination == exec.termination.EXIT && co.code == 0) {
fail(label, strings.concat(drv,
" built ok, expected the collision arg to be rejected"));
};
if (!testenv.has(co.stderr, "not assignable")) {
fail(label, strings.concat(drv, " reject lacked the `not ",
"assignable` diagnostic (a crash, not a clean reject)"));
};
testenv.clean(td);
};
fn shapemismatchmain() str = {
return strings.concat(
"package main;\n",
"import io2;\n",
"export fn main() int = {\n",
" let xs: []int = [1, 2, 3];\n",
" return io2.take(xs);\n",
"};\n");
};
@test fn collide_shape_mismatch() void = {
collidereject("collide_shape_mismatch", "ww", false,
shapemismatchmain());
collidereject("collide_shape_mismatch", "ww_ww", false,
shapemismatchmain());
};
// cstage-only: wwstage's AST-keyed isassignable over-accepts the
// same-leaf same-coarse-shape collision -- the tracked #10/#66/#37
// nominal residual; a dual-stage row would be dark until #37 lands.
@test fn collide_same_shape() void = {
collidereject("collide_same_shape", "ww", true, strings.concat(
"package main;\n",
"import io2;\n",
"import mod1;\n",
"export fn main() int = {\n",
" let s: mod1.stream = mod1.mk();\n",
" return io2.take(s);\n",
"};\n"));
};
// ---- tagged-subset cross-module leaf-bridge rows -----------------------
fn xrow(label: str, esrc: str, msrc: str, expectbuild: bool) void = {
let drvs: []str = ["ww", "ww_ww"];
let i: i32 = 0;
for (i < 2) {
let td: str = testenv.fresh();
testenv.writefile(strings.concat(td, "/e.ww"), esrc);
testenv.writefile(strings.concat(td, "/main.ww"), msrc);
let av: []str = [testenv.driver(drvs[i]), "build", "-I", td,
strings.concat(td, "/main.ww")];
let co: testenv.commandout;
testenv.runcommand(td, td, strings.concat("b_", drvs[i]), av,
tmo(), &co);
let built: bool = co.termination == exec.termination.EXIT
&& co.code == 0;
if (expectbuild) {
if (!built) {
fail(label, strings.concat(drvs[i], " build failed ",
"(leaf bridge dropped? -- bare inline variant vs ",
"qualified consumer spelling)"));
};
let rav: []str = [strings.concat(td, "/main")];
let rc: testenv.commandout;
testenv.runcommand(td, td, strings.concat("r_", drvs[i]),
rav, tmo(), &rc);
if (rc.termination != exec.termination.EXIT || rc.code != 0) {
fail(label, strings.concat(drvs[i], " run-exit != 0"));
};
} else {
if (built) {
fail(label, strings.concat(drvs[i], " built ok, ",
"expected a loud reject (lenient escape over-accept)"));
};
if (!testenv.has(co.stderr, "not assignable")) {
fail(label, strings.concat(drvs[i], " reject lacked the ",
"`not assignable` diagnostic (a crash, not a clean ",
"reject)"));
};
};
testenv.clean(td);
i += 1;
};
};
@test fn xmod_inline_forward() void = {
xrow("xmod_inline_forward", strings.concat(
"package e;\n",
"export type done = !i64;\n",
"export fn next() (i64 | done) = { return 0; };\n"), strings.concat(
"package main;\n",
"import e;\n",
"type myres = (i64 | e.done);\n",
"fn forward() myres = { return e.next(); };\n",
"export fn main() i32 = { return 0; };\n"), true);
};
@test fn xmod_inline_narrow() void = {
xrow("xmod_inline_narrow", strings.concat(
"package e;\n",
"export type done = !i64;\n",
"export type more = !i64;\n",
"export fn next() (i64 | done | more) = { return 0; };\n"),
strings.concat(
"package main;\n",
"import e;\n",
"type myres = (i64 | e.done);\n",
"fn forward() myres = { return e.next(); };\n",
"export fn main() i32 = { return 0; };\n"), false);
};