Files
ww/test/tool/rejects_test.ww

236 lines
7.6 KiB
Plaintext

package rejects_test;
// Compile-reject observers plus the lexical import-shadowing acceptance
// matrix. Ports of the retired native carriers
// test/wcc/708_param_shadow_mod.c, 712_redecl.c and
// 961_opaque_guards.c.
//
// The redeclaration and opaque-guard families below retain their historical
// Cstage-only assertions. Import-name shadowing has the opposite polarity:
// both stages accept a closer lexical value binding, while the package-name
// object remains visible before that binding and after its scope ends. The
// fixture tree is retained because the raw-source route needs a real sibling
// package to exercise selector use and file-local import ownership.
//
// paramshadow (#19/#16) — an import qualifier is a file-scope binding.
// A param/let/mlet/for-range/match-case binding may shadow it in a closer
// lexical scope. Each legacy neg_* fixture now genuinely uses
// `shadowmod.say()` before or in the binding declaration and then returns a
// value computed from the closer binding. The renamed control still runs 42;
// a parameter named like a package imported only by a sibling package still
// runs 2. The carrier's neg_selfimp leg remains dropped here:
// test/wcc/data/r948_selfimport/case.ww owns the identical claim
// (`//ww:error "self-import"`, both frontends).
//
// redecl (#32) — same-scope let/mlet/param/top-let redeclarations
// reject at every check.c site whose scope_define NULL return was
// once silently ignored (last-write-wins miscompile class).
//
// opaque guards — opaque by value (local/param/return/struct
// field/array elem/tuple/tagged member) and indexing []opaque reject.
// The both-stage size/align rejects and the *opaque / []opaque
// accepts are owned by the r961_* fixtures and test/lang.
//
// Dropped C machinery, not assertions: the per-path 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("rejects 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;
};
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
fn runcode(dir: str, root: str, name: str, argv: []str) i32 = {
let co: testenv.commandout;
testenv.runcommand(dir, root, name, argv, tmo(), &co);
if (co.termination != exec.termination.EXIT) { return -1; };
return co.code;
};
fn fixdir() str = {
return strings.concat(testenv.repo(), "/test/wcc/data/paramshadowmod");
};
fn paramshadow_case(src: str, label: str, want: i32) void = {
let stages: []str = ["ww", "ww_ww"];
let tags: []str = ["c", "ww"];
let i: i32 = 0;
for (i < stages.len) {
let td: str = testenv.fresh();
let out: str = strings.concat(td, "/out");
// cwd is the fixture dir so source-dir-first import search resolves
// `import shadowmod;`; -o keeps every product outside the tree.
let av: []str = [testenv.driver(stages[i]), "build", "-o", out, src];
let stem: str = strings.concat(label, "_", tags[i]);
if (runcode(fixdir(), td, strings.concat("build_", stem), av) != 0) {
fail(stem, "build failed -- lexical import shadowing was rejected");
};
let rav: []str = [out];
if (runcode(td, td, strings.concat("run_", stem), rav) != want) {
fail(stem, "built binary exit != exact expected value");
};
testenv.clean(td);
i += 1;
};
};
@test fn paramshadow_lexical_bindings() void = {
let tags: []str = ["param", "let", "mlet", "forrange_single",
"forrange_tuple", "mcase"];
let wants: []i32 = [44, 42, 45, 107, 45, 42];
let i: i32 = 0;
for (i < tags.len) {
paramshadow_case(strings.concat("neg_", tags[i], ".ww"), tags[i],
wants[i]);
i += 1;
};
};
@test fn paramshadow_pos_rename() void = {
paramshadow_case("pos_rename.ww", "pos_rename", 42);
};
@test fn paramshadow_pos_crossmod() void = {
paramshadow_case("pos_crossmod.ww", "pos_crossmod", 2);
};
fn rejectrows(family: str, labels: []str, srcs: []str) void = {
let i: i32 = 0;
for (i < labels.len) {
let td: str = testenv.fresh();
let src: str = strings.concat(td, "/src.ww");
testenv.writefile(src, srcs[i]);
let av: []str = [testenv.driver("ww"), "build", "-o",
strings.concat(td, "/out"), src];
if (runcode(td, td, labels[i], av) == 0) {
fail(strings.concat(family, ".", labels[i]),
"build unexpectedly succeeded");
};
testenv.clean(td);
i += 1;
};
};
// The check.c sites named per row are the scope_define NULL returns
// the #32 fix turned into errors.
@test fn redecl() void = {
let labels: []str = ["neg_let_same_block", "neg_mlet_same_block",
"neg_mlet_tuple_dup", "neg_forrange_tuple_dup", "neg_param_dup",
"neg_toplet_dup"];
let srcs: []str = [
// site 1443 -- block-body let dup
strings.concat(
"package main;\n",
"fn main() i32 = {\n",
" let a: i32 = 1;\n",
" let a: i32 = 2;\n",
" return a;\n",
"};\n"),
// site 1562 -- mlet shadows earlier same-block let
strings.concat(
"package main;\n",
"fn pair() (i32, i32) = { return (10, 20); };\n",
"fn main() i32 = {\n",
" let a: i32 = 1;\n",
" let (a, b) = pair();\n",
" return a + b;\n",
"};\n"),
// site 1562 -- mlet pattern lists the same name twice
strings.concat(
"package main;\n",
"fn pair() (i32, i32) = { return (10, 20); };\n",
"fn main() i32 = {\n",
" let (a, a) = pair();\n",
" return a;\n",
"};\n"),
// site 1505 -- forrange tuple-pattern lists same name twice
strings.concat(
"package main;\n",
"fn main() i32 = {\n",
" let xs: [1](i32, i32) = [(10i32, 20i32)];\n",
" for (let (a, a) .. xs) {\n",
" return a;\n",
" };\n",
" return -1;\n",
"};\n"),
// site 1914 -- two params with the same name
strings.concat(
"package main;\n",
"fn f(a: i32, a: i32) i32 = { return a; };\n",
"fn main() i32 = { return f(1, 2); };\n"),
// site 1880 -- top-level let dup
strings.concat(
"package main;\n",
"let x: i32 = 1;\n",
"let x: i32 = 2;\n",
"fn main() i32 = { return x; };\n")];
rejectrows("redecl", labels, srcs);
};
@test fn opaque_guards() void = {
let labels: []str = ["neg_bare_local", "neg_param", "neg_return",
"neg_struct_field", "neg_array_elem", "neg_slice_index",
"neg_tuple_member", "neg_tagged_variant"];
let srcs: []str = [
strings.concat(
"package main;\n",
"export fn main() i32 = {\n",
"\tlet x: opaque;\n",
"\treturn 0;\n",
"};\n"),
strings.concat(
"package main;\n",
"fn f(x: opaque) i32 = { return 0; };\n",
"export fn main() i32 = { return 0; };\n"),
// a declaration isolates the return-type guard: a body returning
// 0 would add an unrelated untyped_int-to-opaque rejection
strings.concat(
"package main;\n",
"fn f() opaque;\n",
"export fn main() i32 = { return 0; };\n"),
strings.concat(
"package main;\n",
"type S = struct { x: opaque };\n",
"export fn main() i32 = { return 0; };\n"),
strings.concat(
"package main;\n",
"export fn main() i32 = {\n",
"\tlet a: [4]opaque;\n",
"\treturn 0;\n",
"};\n"),
// the cast keeps this row on the slice-index guard rather than
// creating a bare opaque local
strings.concat(
"package main;\n",
"export fn main() i32 = {\n",
"\tlet s: []opaque;\n",
"\tlet v: i32 = s[0]: i32;\n",
"\treturn v;\n",
"};\n"),
strings.concat(
"package main;\n",
"export fn main() i32 = {\n",
"\tlet t: (opaque, i32);\n",
"\treturn 0;\n",
"};\n"),
strings.concat(
"package main;\n",
"export fn main() i32 = {\n",
"\tlet x: (opaque | i32);\n",
"\treturn 0;\n",
"};\n")];
rejectrows("opaque", labels, srcs);
};