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:
253
test/xmod/typecheck_test.ww
Normal file
253
test/xmod/typecheck_test.ww
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user