/* * 712_redecl — check: refuse same-scope let/mlet/param/top-let * redeclarations (task #32). * * Pre-fix: cmd/wcc/check.c silently dropped the duplicate insert when * scope_define returned NULL. `let a: i32 = 1; let a: i32 = 2;` in * one block compiled cleanly and the second store last-write-wins; * `fn f(a: i32, a: i32)` accepted both params, body resolved to the * second. Surfaced post-#27 (which dropped the localoff name-dedup * shim that had been masking the issue at codegen) — see commit * 1292f98's follow-up note and worker-27's filed task. * * Post-fix: cstage check.c errors at every site where scope_define / * scope_define_in_module's NULL return was previously ignored: * * site | message * ---------------------------+-------------------------------------- * block-body N_LET | let '%s' redeclared in same scope * N_MLET (`let (a,b)=…`) | let '%s' redeclared in same scope * N_FORRANGE tuple-binding | binding '%s' redeclared in same scope * N_FNDECL param | param '%s' redeclared * top-level N_LET | duplicate let %s * * N_MCASE (case-binding) and N_FORRANGE single-binding already lived * in fresh per-arm / per-loop scopes with at most one bind, so they * weren't part of the bug class. Top-level N_TYPEDECL / N_DEF / * N_FNDECL already errored on NULL ("duplicate %s"). * * Cstage-only. Wwstage's check.ww is a single-pass resolve walk with * no per-block scoping (line 1015 comment), exercised only by * wwdump_ww as a diagnostic; adding the guard there today would * false-positive on legal cross-block shadow. Tracked under #11 * (wwstage checkfile pass with per-block scoping). Matches the * test/wcc/708 + test/wcc/696 cstage-only neg-case precedent. * * row | gate | what it pins * --------------------------+----------------+-------------------- * neg_let_same_block | build fails | site 1443 * neg_mlet_same_block | build fails | site 1562 * neg_mlet_tuple_dup | build fails | site 1562 (dup-in) * neg_forrange_tuple_dup | build fails | site 1505 * neg_param_dup | build fails | site 1914 * neg_toplet_dup | build fails | site 1880 * The four legal-shadow positive rows are owned by the directive fixtures * under test/wcc/data/r71_redecl_*. This residual wrapper intentionally owns * only the six C-stage-only rejects below: wwstage lacks per-block scoping, * so these cannot be represented by the symmetric //ww:error contract. */ #include #include #include #include #include #include #include #include "wwtestpkg.h" static int runwait(const char *cmd) { int rc = system(cmd); if (rc == -1) return -1; if (WIFEXITED(rc)) return WEXITSTATUS(rc); return -1; } struct row { const char *label; const char *src; }; static const struct row rows[] = { /* neg: site 1443 — block-body let dup. */ { "neg_let_same_block", "fn main() i32 = {\n" " let a: i32 = 1;\n" " let a: i32 = 2;\n" " return a;\n" "};\n" }, /* neg: site 1562 — mlet shadows earlier same-block let. */ { "neg_mlet_same_block", "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" }, /* neg: site 1562 — mlet pattern lists the same name twice. */ { "neg_mlet_tuple_dup", "fn pair() (i32, i32) = { return (10, 20); };\n" "fn main() i32 = {\n" " let (a, a) = pair();\n" " return a;\n" "};\n" }, /* neg: site 1505 — forrange tuple-pattern lists same name twice. */ { "neg_forrange_tuple_dup", "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" }, /* neg: site 1914 — two params with the same name. */ { "neg_param_dup", "fn f(a: i32, a: i32) i32 = { return a; };\n" "fn main() i32 = { return f(1, 2); };\n" }, /* neg: site 1880 — top-level let dup. */ { "neg_toplet_dup", "let x: i32 = 1;\n" "let x: i32 = 2;\n" "fn main() i32 = { return x; };\n" }, }; static int run_row(const char *driver, const struct row *r, int i) { char src[128], tmpdir[128], outbin[128], sepdir[160], rmcmd[192]; char cmd[2048]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wcredecl_%d_d_%d", getpid(), i); if (mkdir(tmpdir, 0755) != 0) { fprintf(stderr, "redecl[%s]: mkdir %s: %s\n", r->label, tmpdir, strerror(errno)); return -1; } snprintf(src, sizeof src, "%s/wcredecl_%d_%d.ww", tmpdir, getpid(), i); snprintf(outbin, sizeof outbin, "%s/wcredecl_%d_%d", tmpdir, getpid(), i); snprintf(sepdir, sizeof sepdir, "%s.sepwork", outbin); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir); int failed = 0, rc; FILE *f = fopen(src, "wb"); if (!f) { fprintf(stderr, "redecl[%s]: fopen %s: %s\n", r->label, src, strerror(errno)); failed = 1; goto cleanup; } wwtest_fputs(r->src, f); if (ferror(f)) { fprintf(stderr, "redecl[%s]: write %s failed\n", r->label, src); failed = 1; } if (fclose(f) != 0) { fprintf(stderr, "redecl[%s]: close %s: %s\n", r->label, src, strerror(errno)); failed = 1; } if (failed) goto cleanup; snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1", driver, outbin, src); rc = runwait(cmd); if (rc == 0) { fprintf(stderr, "redecl[%s]: build unexpectedly succeeded\n", r->label); failed = 1; } cleanup: { int cleanup_failed = 0; if (runwait(rmcmd) != 0) { fprintf(stderr, "redecl[%s]: remove %s failed\n", r->label, sepdir); cleanup_failed = 1; } if (unlink(src) != 0 && errno != ENOENT) { fprintf(stderr, "redecl[%s]: unlink %s: %s\n", r->label, src, strerror(errno)); cleanup_failed = 1; } if (unlink(outbin) != 0 && errno != ENOENT) { fprintf(stderr, "redecl[%s]: unlink %s: %s\n", r->label, outbin, strerror(errno)); cleanup_failed = 1; } if (rmdir(tmpdir) != 0) { fprintf(stderr, "redecl[%s]: rmdir %s: %s\n", r->label, tmpdir, strerror(errno)); cleanup_failed = 1; } if (!failed && cleanup_failed) failed = 1; } return failed ? -1 : 0; } int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char absbin[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); int n = (int)(sizeof rows / sizeof rows[0]); int fail = 0; for (int i = 0; i < n; i++) { if (run_row(cdrv, &rows[i], i) != 0) fail++; } if (fail) { fprintf(stderr, "redecl: %d/%d row(s) failed\n", fail, n); return 1; } printf("redecl: %d/%d ok\n", n, n); return 0; }