Ports 708_param_shadow_mod, 712_redecl and 961_opaque_guards into test/tool/rejects_test.ww on testenv, one commit because the three carriers share the single asymmetric-polarity claim (cstage rejects, wwstage accepts) that a both-stage //ww:error fixture cannot carry. The neg_selfimp leg drops: test/wcc/data/r948_selfimport/case.ww owns the identical both-frontend claim.
245 lines
8.0 KiB
Plaintext
245 lines
8.0 KiB
Plaintext
package rejects_test;
|
|
|
|
// Cstage-only compile-reject observers on the `ww` driver. Ports of
|
|
// the retired native carriers test/wcc/708_param_shadow_mod.c,
|
|
// 712_redecl.c and 961_opaque_guards.c; every assertion preserved.
|
|
//
|
|
// ASYMMETRIC polarity, all three families: the reject lives only in
|
|
// cstage check.c. Wwstage's check.ww is a single-pass resolve walk
|
|
// with no per-block scoping (#11) and no #108(b) require_sized
|
|
// construction/binding guards, so it ACCEPTS these programs — the
|
|
// both-stage //ww:error fixture contract cannot carry them
|
|
// (residual-carrier-audit.json, 708/712/961 entries). Each row
|
|
// asserts the CSTAGE reject only; when wwstage gains the rule (#11
|
|
// per-block scoping; the #108(b) guards), the rows graduate to
|
|
// //ww:error fixtures and this observer shrinks. The paramshadow neg
|
|
// rows additionally require the sibling-module fixture tree, so they
|
|
// stay here until a single-file multi-package repro is validated.
|
|
//
|
|
// paramshadow (#19/#16) — value names and module names are disjoint:
|
|
// a param/let/mlet/for-range/match-case binding named `shadowmod` in
|
|
// a file importing module shadowmod is rejected at the decl site; the
|
|
// renamed binding builds+runs 42 (no over-trigger); a param named
|
|
// like a module a SIBLING module imports does not trip (src_imports
|
|
// filters by the binding's own module — build+run exit 2). The
|
|
// carrier's neg_selfimp leg is 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");
|
|
};
|
|
|
|
@test fn paramshadow_neg() void = {
|
|
let tags: []str = ["param", "let", "mlet", "forrange_single",
|
|
"forrange_tuple", "mcase"];
|
|
let i: i32 = 0;
|
|
for (i < tags.len) {
|
|
let td: str = testenv.fresh();
|
|
// cwd is the fixture dir so the driver's source-dir-first import
|
|
// search resolves `import shadowmod;`; -o keeps the binary and
|
|
// its sepwork out of the tracked tree.
|
|
let av: []str = [testenv.driver("ww"), "build", "-o",
|
|
strings.concat(td, "/out"),
|
|
strings.concat("neg_", tags[i], ".ww")];
|
|
if (runcode(fixdir(), td, strings.concat("neg_", tags[i]), av)
|
|
== 0) {
|
|
fail(strings.concat("neg_", tags[i]),
|
|
"build unexpectedly succeeded -- shadow rule did not fire");
|
|
};
|
|
testenv.clean(td);
|
|
i += 1;
|
|
};
|
|
};
|
|
|
|
fn paramshadow_pos(src: str, label: str, want: i32, why: str) void = {
|
|
let td: str = testenv.fresh();
|
|
let out: str = strings.concat(td, "/out");
|
|
let av: []str = [testenv.driver("ww"), "build", "-o", out, src];
|
|
if (runcode(fixdir(), td, strings.concat("build_", label), av) != 0) {
|
|
fail(label, why);
|
|
};
|
|
let rav: []str = [out];
|
|
if (runcode(td, td, strings.concat("run_", label), rav) != want) {
|
|
fail(label, "built binary exit != expected");
|
|
};
|
|
testenv.clean(td);
|
|
};
|
|
|
|
@test fn paramshadow_pos_rename() void = {
|
|
paramshadow_pos("pos_rename.ww", "pos_rename", 42,
|
|
"build failed -- rule over-triggered after the rename");
|
|
};
|
|
|
|
@test fn paramshadow_pos_crossmod() void = {
|
|
paramshadow_pos("pos_crossmod.ww", "pos_crossmod", 2, strings.concat(
|
|
"build failed -- shadow rule over-triggered on a ",
|
|
"cross-module param"));
|
|
};
|
|
|
|
// One `ww build` per inline source; the build must exit nonzero.
|
|
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);
|
|
};
|