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 in a real `package main` 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")); }; 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); };