wcc: general call-arg typecheck via assignability union, both stages
wwstage's desugarcallargs ran no general per-arg typecheck (only the narrow #258 array-to-slice arm): any mistyped scalar call-arg silently miscompiled (int read as a 24B slice header; the -T face was a user const __wwtests building a garbage test binary). Route every call-arg through the predicate union isassignable()||assignableaddrfn(), mirroring cstage type_assignable||assignable_addrfn and the check.c:1869 diagnostic. Confident scalar/aggregate and aggregate/aggregate kind-mismatch rejects live in shared isassignable; the concrete-to- tagged arm is shape-matched-lenient via tagshape() (AST mirror of cgen taggedvariantindext) so genuine variant members keep flowing while shape-mismatched aggregates reject. Reserve __wwtests under -T in both stages (mirror the main reservation, check.c:2996). New table-driven 989_callarg_typecheck, 31 fixtures, reject rows proven red on pre-fix binaries. Deferred, filed, site-commented: the assign seam rides #178->#36 (typeeqast cannot compare variadic/module-qualified fn sigs); the same-coarse-shape same-leaf nominal collision over-accept rides #37 (#10/#66 — the distinguishing module is absent from the AST surface isassignable operates on).
This commit is contained in:
543
test/wcc/989_callarg_typecheck.c
Normal file
543
test/wcc/989_callarg_typecheck.c
Normal file
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
* 989_callarg_typecheck — #24: wwstage align UP to cstage on GENERAL
|
||||
* call-argument assignability (CHECKER-ONLY, cat-A silent-miscompile).
|
||||
*
|
||||
* THE BUG: wwstage ran NO general call-arg typecheck. desugarcallargs
|
||||
* (selfhost/cmd/wcc/check.ww) only had the narrow #258 array→slice arm,
|
||||
* so ANY mistyped non-array scalar call-arg was SILENTLY accepted: an
|
||||
* `int` passed where a `[]T` param expects a 24B slice header builds
|
||||
* rc=0 and runs garbage (the int's 8 bytes read as the slice .len/.ptr).
|
||||
* cstage rejects every non-variadic arg at cmd/wcc/check.c:1867-1870
|
||||
* (`argument type %s not assignable to %s`).
|
||||
*
|
||||
* The -T face is the headline silent miscompile: a user
|
||||
* `const __wwtests: int` + ≥1 @test fn shadows the synth test table, so
|
||||
* the synth `run(__wwtests)` passes the int 99 where run() wants the
|
||||
* `[](str, *fn() void)` table — cstage rejected it loud, wwstage
|
||||
* silently built a broken binary that iterated 99 as a slice .len and
|
||||
* printed 63 garbage FAIL rows / crashed.
|
||||
*
|
||||
* THE FIX is two stacked concerns, both align-UP-to-cstage:
|
||||
* (a) general per-arg isassignable at the desugarcallargs choke-point,
|
||||
* mirroring check.c:1867 (conf-gated like the let/return sibling
|
||||
* sites; subsumes the old narrow #258 reject arm).
|
||||
* (b) reserve the synth `__wwtests` name under -T in BOTH stages,
|
||||
* mirroring the `main` reservation (check.c:2996 / check.ww). A
|
||||
* user `__wwtests` whose type HAPPENS to match run()'s param
|
||||
* ([](str,*fn()void)) slips (a) but still silently shadows the
|
||||
* synth table → reservation rejects it loud regardless of type.
|
||||
*
|
||||
* Rows (every row builds+runs on cstage `ww` and, when present, wwstage
|
||||
* `ww_ww`; rule-10 — both stages must agree):
|
||||
* row | shape | verdict
|
||||
* ---------------------+------------------------------------+----------
|
||||
* scalar_for_slice | takesslice(int) | REJECT [bug]
|
||||
* str_for_int | takesint(str) | REJECT
|
||||
* slice_borrow_ok | takesslice([3]int arr) | ok 0 (#258)
|
||||
* scalar_arg_ok | takesint(5) | ok 7 (control)
|
||||
*
|
||||
* -T faces (bundle via `ww test -c`, then `<comp> -T <combined>` must
|
||||
* loud-reject; the synth's run() callee is resolved from the bundled
|
||||
* lib/test):
|
||||
* wwtests_int | const __wwtests: int | REJECT [(a)]
|
||||
* wwtests_shadow | const __wwtests: [](str,*fn()void) | REJECT [(b)]
|
||||
*
|
||||
* wwtests_shadow is the (b) teeth: its type matches run()'s param so (a)
|
||||
* stays silent (both unpatched stages built it rc=0, silently running
|
||||
* the user's 1-entry table instead of the collected @test set) — only
|
||||
* the name reservation rejects it.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.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;
|
||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
||||
int want_exit; /* meaningful only when expect_build */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* (1) THE BUG — int where []int is wanted → REJECT both stages.
|
||||
* Pre-fix wwstage built rc=0 and read the int as a slice header. */
|
||||
{ "scalar_for_slice",
|
||||
"package main;\n"
|
||||
"fn takesslice(xs: []int) void = { return; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let n: int = 5;\n"
|
||||
" takesslice(n);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0 },
|
||||
|
||||
/* (2) str where int is wanted → REJECT both stages. A second scalar
|
||||
* shape: the general check is not array-specific. */
|
||||
{ "str_for_int",
|
||||
"package main;\n"
|
||||
"fn takesint(x: int) void = { return; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let s: str = \"hi\";\n"
|
||||
" takesint(s);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0 },
|
||||
|
||||
/* (3) CONTROL — [3]int into a []int param is the legit #258 borrow
|
||||
* (matching element) → ACCEPT both, run to 0. The general check must
|
||||
* NOT regress the array→slice borrow it subsumes. */
|
||||
{ "slice_borrow_ok",
|
||||
"package main;\n"
|
||||
"fn takesslice(xs: []int) int = { return len(xs): int; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let a: [3]int = [1, 2, 3];\n"
|
||||
" if (takesslice(a) != 3) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1, 0 },
|
||||
|
||||
/* (4) CONTROL — a correctly-typed scalar arg → ACCEPT both, run to 7.
|
||||
* Pins that the new check does not over-reject a valid scalar call. */
|
||||
{ "scalar_arg_ok",
|
||||
"package main;\n"
|
||||
"fn takesint(x: int) int = { return x; };\n"
|
||||
"export fn main() int = { return takesint(7); };\n",
|
||||
1, 7 },
|
||||
|
||||
/* (5) scalar→aggregate at the LET context — `let xs: []int = 5` read
|
||||
* the int as a 24B slice header (same silent-garbage class as the
|
||||
* call-arg #24, via the SHARED isassignable). REJECT both. */
|
||||
{ "let_slice_eq_scalar",
|
||||
"package main;\n"
|
||||
"export fn main() int = { let xs: []int = 5; return 0; };\n",
|
||||
0, 0 },
|
||||
|
||||
/* (6) bare fn name into a fn-alias param → ACCEPT both (#34/c1': a
|
||||
* bare fn rvalue types as its fn TYPE; the general check then sees a
|
||||
* matched fn type). run to 7. */
|
||||
{ "fn_bare_accept",
|
||||
"package main;\n"
|
||||
"type myfn = fn() int;\n"
|
||||
"fn g() int = { return 7; };\n"
|
||||
"fn use_it(f: myfn) int = { return f(); };\n"
|
||||
"export fn main() int = { return use_it(g); };\n",
|
||||
1, 7 },
|
||||
|
||||
/* (7) annotated `g: myfn` into the same param → ACCEPT both, run 7. */
|
||||
{ "fn_cast_accept",
|
||||
"package main;\n"
|
||||
"type myfn = fn() int;\n"
|
||||
"fn g() int = { return 7; };\n"
|
||||
"fn use_it(f: myfn) int = { return f(); };\n"
|
||||
"export fn main() int = { return use_it(g: myfn); };\n",
|
||||
1, 7 },
|
||||
|
||||
/* (8) `&g` (*fn) into a bare-fn alias param → REJECT both. ww-cstage
|
||||
* takes NO &-required rule for a bare-fn arg, and the *fn-vs-fn KIND
|
||||
* mismatch is a confident reject (the assignableaddrfn union admits a
|
||||
* genuine &fn only into a *fn / *alias slot, not a bare-fn alias). */
|
||||
{ "fn_addr_reject",
|
||||
"package main;\n"
|
||||
"type myfn = fn() int;\n"
|
||||
"fn g() int = { return 7; };\n"
|
||||
"fn use_it(f: myfn) int = { return f(); };\n"
|
||||
"export fn main() int = { return use_it(&g); };\n",
|
||||
0, 0 },
|
||||
|
||||
/* (9) matched-signature fn rvalue into a fn slot → ACCEPT both, run 5.
|
||||
* Pins #34's correct-stamp path (fn type vs fn type, equal sigs). */
|
||||
{ "fn_match_sig_accept",
|
||||
"package main;\n"
|
||||
"fn g() int = { return 5; };\n"
|
||||
"export fn main() int = { let p: fn() int = g; return p(); };\n",
|
||||
1, 5 },
|
||||
|
||||
/* (10) MISMATCHED-signature fn rvalue into a fn slot → REJECT both
|
||||
* (#34: `let p: fn() int = h` where h: fn() str — was a silent
|
||||
* mis-accept via the lenient catch-all; the fn-type stamp now compares
|
||||
* structurally and rejects). */
|
||||
{ "fn_mismatch_sig_reject",
|
||||
"package main;\n"
|
||||
"fn h() str = { return \"\"; };\n"
|
||||
"export fn main() int = { let p: fn() int = h; return 0; };\n",
|
||||
0, 0 },
|
||||
|
||||
/* (11) aggregate<->aggregate KIND mismatch — a [3]int array arg into a
|
||||
* `*int` param. Both aggregate, different kind -> confident reject (the
|
||||
* array->slice borrow is the ONLY implicit array coercion; array->ptr
|
||||
* is not). REJECT both. */
|
||||
{ "aggr_array_into_ptr",
|
||||
"package main;\n"
|
||||
"fn takesptr(p: *int) void = { return; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let a: [3]int = [1, 2, 3];\n"
|
||||
" takesptr(a);\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0, 0 },
|
||||
|
||||
/* (12) CONTROL — a concrete value into a SPREAD-tagged param must NOT
|
||||
* over-reject: cstage flattens `...inner` at resolve_type and accepts
|
||||
* the int leaf; wwstage can't flatten, so it stays LENIENT on a spread
|
||||
* variant (the faithful escape, mirroring tagged->tagged #115). ACCEPT
|
||||
* both, run 0. Pins the new check does not regress spread unions. */
|
||||
{ "spread_tagged_accept",
|
||||
"package main;\n"
|
||||
"type inner = (int | str);\n"
|
||||
"fn take(x: (...inner | bool)) void = { return; };\n"
|
||||
"export fn main() int = { take(42); return 0; };\n",
|
||||
1, 0 },
|
||||
};
|
||||
|
||||
/* run_build — build+run `src` via `driver`; returns the binary's exit
|
||||
* code, or -1 on a build failure. */
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/cat_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/cat_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
/* build_should_fail — the build must error on `driver`; returns 0 when
|
||||
* it correctly FAILS, non-zero when it wrongly succeeded. */
|
||||
static int
|
||||
build_should_fail(const char *driver, const char *src, int i)
|
||||
{
|
||||
char s[64], tmpdir[64], cmd[1024];
|
||||
snprintf(s, sizeof s, "/tmp/catn_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/catn_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(s, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, s);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
unlink(s);
|
||||
const char *base = strrchr(s, '/');
|
||||
base = base ? base + 1 : s;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
unlink(outbin);
|
||||
rmdir(tmpdir);
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
/* -T reject faces — a user fixture is bundled with lib/test via
|
||||
* `ww test -c` (compiler-neutral; the synth `run()` callee resolves from
|
||||
* the bundle), then `<comp> -T <combined>` must loud-reject. */
|
||||
struct trow {
|
||||
const char *label;
|
||||
const char *src;
|
||||
};
|
||||
|
||||
static const struct trow trows[] = {
|
||||
/* (a) the headline cat-A: user __wwtests:int shadows the synth table
|
||||
* → run(int) where run wants [](str,*fn()void). Pre-fix wwstage built
|
||||
* a broken binary (int read as a 24B slice header). */
|
||||
{ "wwtests_int",
|
||||
"package main;\n"
|
||||
"const __wwtests: int = 99;\n"
|
||||
"@test fn checkfoo() void = { return; };\n" },
|
||||
|
||||
/* (b) reservation teeth: a user __wwtests whose type MATCHES run()'s
|
||||
* param typechecks fine — (a)'s isassignable stays silent — but it
|
||||
* silently shadows the synth table (both unpatched stages built it
|
||||
* rc=0, running the user's 1-entry table, not the collected @tests).
|
||||
* Only the `__wwtests` name reservation rejects it. */
|
||||
{ "wwtests_shadow",
|
||||
"package main;\n"
|
||||
"fn dummy() void = { return; };\n"
|
||||
"const __wwtests: [](str, *fn() void) = [(\"x\", &dummy)];\n"
|
||||
"@test fn checkfoo() void = { return; };\n" },
|
||||
};
|
||||
|
||||
/* tbundle_reject — bundle `src` via `drv test -c`, then `comp -T` the
|
||||
* combined unit; the compile must exit nonzero. Returns 0 on the
|
||||
* expected reject. */
|
||||
static int
|
||||
tbundle_reject(const char *bin, const char *comp, const char *drv,
|
||||
const struct trow *t, int i)
|
||||
{
|
||||
int pid = getpid();
|
||||
char src[128], stem[128], comb[160], cmd[4096];
|
||||
snprintf(src, sizeof src, "/tmp/catt_%s_%d_%d.ww", comp, pid, i);
|
||||
snprintf(stem, sizeof stem, "/tmp/catt_%s_%d_%d", comp, pid, i);
|
||||
snprintf(comb, sizeof comb, "%s.combined.ww", stem);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(t->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s/%s test -c -o %s %s > /dev/null 2>&1", bin, drv, stem, src);
|
||||
runwait(cmd);
|
||||
if (access(comb, 0) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck[%s][%s]: %s produced no %s\n",
|
||||
comp, t->label, drv, comb);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -T %s -o /dev/null 2>/dev/null",
|
||||
bin, comp, comb);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
unlink(src); unlink(comb);
|
||||
char tmp[200];
|
||||
snprintf(tmp, sizeof tmp, "%s.s", stem); unlink(tmp);
|
||||
snprintf(tmp, sizeof tmp, "%s.o", stem); unlink(tmp);
|
||||
unlink(stem);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "callarg_typecheck[%s][%s]: %s -T accepted "
|
||||
"(expected a loud reject)\n", comp, t->label, comp);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* multimod_build_fail — write io2.ww (always; defines handle=(file|stream)),
|
||||
* mod1.ww (when withmod1), and a main.ww with `mainbody`, then `drv build -I`
|
||||
* the tree. Returns 0 when the build correctly FAILS (the arg is rejected),
|
||||
* non-zero when it wrongly built. Drives the #24 cross-module collision rows
|
||||
* that single-file rows[] can't express. */
|
||||
static int
|
||||
multimod_build_fail(const char *drv, const char *mainbody, int withmod1,
|
||||
int tag)
|
||||
{
|
||||
int pid = getpid();
|
||||
char dir[96], io2d[160], mod1d[160], p[224], cmd[2048], rm[256];
|
||||
snprintf(dir, sizeof dir, "/tmp/catcoll_%d_%d", pid, tag);
|
||||
snprintf(io2d, sizeof io2d, "%s/io2", dir);
|
||||
snprintf(mod1d, sizeof mod1d, "%s/mod1", dir);
|
||||
mkdir(dir, 0755); mkdir(io2d, 0755);
|
||||
if (withmod1) mkdir(mod1d, 0755);
|
||||
|
||||
snprintf(p, sizeof p, "%s/io2.ww", io2d);
|
||||
FILE *f = fopen(p, "wb");
|
||||
if (!f) return -1;
|
||||
fputs("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", f);
|
||||
fclose(f);
|
||||
|
||||
if (withmod1) {
|
||||
snprintf(p, sizeof p, "%s/mod1.ww", mod1d);
|
||||
f = fopen(p, "wb");
|
||||
if (!f) return -1;
|
||||
fputs("package mod1;\n"
|
||||
"export type wbox = struct { y: i64 };\n"
|
||||
"export type stream = *wbox;\n"
|
||||
"export fn mk() stream = { return nil; };\n", f);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
snprintf(p, sizeof p, "%s/main.ww", dir);
|
||||
f = fopen(p, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(mainbody, f);
|
||||
fclose(f);
|
||||
|
||||
if (withmod1)
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build -I %s -I %s main.ww >/dev/null 2>&1",
|
||||
dir, drv, io2d, mod1d);
|
||||
else
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build -I %s main.ww >/dev/null 2>&1",
|
||||
dir, drv, io2d);
|
||||
int rc = runwait(cmd);
|
||||
|
||||
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
||||
runwait(rm);
|
||||
|
||||
return rc == 0 ? -1 : 0; /* build must NOT succeed */
|
||||
}
|
||||
|
||||
/* a []int SLICE passed where io2.handle = (file | stream) is wanted — NO
|
||||
* slice variant. SHAPE-MISMATCH: cstage rejects nominally, and c3's (B)
|
||||
* shape-matched-lenient leg ALSO rejects (slice src, no slice-shape variant)
|
||||
* — the #24-B reachable win, asserted DUAL-STAGE (both drivers must fail). */
|
||||
static const char COLLIDE_SHAPE_MISMATCH[] =
|
||||
"package main;\n"
|
||||
"import io2;\n"
|
||||
"export fn main() int = {\n"
|
||||
" let xs: []int = [1, 2, 3];\n"
|
||||
" return io2.take(xs);\n"
|
||||
"};\n";
|
||||
|
||||
/* mod1.stream (a *mod1.wbox) passed where io2.handle is wanted — a cross-
|
||||
* module SAME-LEAF, SAME-COARSE-SHAPE (both ptr → scalar/other) collision.
|
||||
* cstage REJECTS on NOMINAL identity; wwstage's AST-keyed isassignable
|
||||
* OVER-ACCEPTS it — the tracked #10/#66/#37 residual that (B) shape-narrowing
|
||||
* cannot reach (the tagged variant node is a bare `stream` with no module,
|
||||
* byte-identical to the genuine io2.stream; the distinguishing identity lives
|
||||
* only in tinfo/#66, reachable only by the #37 nominal-tinfo conversion). The
|
||||
* cs-side pin below asserts ONLY cstage's reject (catching a cs regression +
|
||||
* recording the eventual ww target); the ww over-accept is documented, NOT
|
||||
* asserted (a dual-stage row would be dark until #37 lands). */
|
||||
static const char COLLIDE_SAME_SHAPE[] =
|
||||
"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";
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024], wdrv[1024], wcomp[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
snprintf(wcomp, sizeof wcomp, "%s/w6c_ww", bin);
|
||||
|
||||
struct { const char *name; const char *drv; int gated; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int tn = (int)(sizeof trows / sizeof trows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].drv);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (rows[i].expect_build) {
|
||||
int got = run_build(drivers[d].drv, &rows[i], i);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "callarg_typecheck[%s][%s]: "
|
||||
"exit=%d want=%d\n", drivers[d].name,
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
} else {
|
||||
if (build_should_fail(drivers[d].drv, rows[i].src,
|
||||
100 + i) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck[%s][%s]: "
|
||||
"built ok, expected a loud reject\n",
|
||||
drivers[d].name, rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -T faces: comp ∈ {w6c (cstage), w6c_ww (wwstage)}; bundle is built
|
||||
* by the cstage driver (compiler-neutral). wwstage gated. */
|
||||
for (int i = 0; i < tn; i++) {
|
||||
total++;
|
||||
if (tbundle_reject(bin, "w6c", "ww", &trows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
if (access(wcomp, X_OK) == 0) {
|
||||
for (int i = 0; i < tn; i++) {
|
||||
total++;
|
||||
if (tbundle_reject(bin, "w6c_ww", "ww", &trows[i],
|
||||
10 + i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* #24-B reachable win: the SHAPE-MISMATCH collision ([]int slice into a
|
||||
* (file|stream) tagged with no slice variant) REJECTS on BOTH stages —
|
||||
* dual-stage row (cstage nominal + c3's shape-matched-lenient leg). */
|
||||
total++;
|
||||
if (multimod_build_fail(cdrv, COLLIDE_SHAPE_MISMATCH, 0, 1) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck[cstage][collide_shape_mismatch]:"
|
||||
" built ok, expected reject\n");
|
||||
fail++;
|
||||
}
|
||||
if (access(wcomp, X_OK) == 0) { /* wwstage gated (ww_ww present) */
|
||||
total++;
|
||||
if (multimod_build_fail(wdrv, COLLIDE_SHAPE_MISMATCH, 0, 2) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck[wwstage]"
|
||||
"[collide_shape_mismatch]: built ok, expected "
|
||||
"reject (#24-B shape-matched-lenient)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
/* #37 cs-side pin: the SAME-SHAPE cross-module collision rejects on
|
||||
* cstage; the wwstage over-accept is the documented #10/#66/#37 nominal
|
||||
* residual (NOT asserted — would be dark until #37 lands). */
|
||||
total++;
|
||||
if (multimod_build_fail(cdrv, COLLIDE_SAME_SHAPE, 1, 3) != 0) {
|
||||
fprintf(stderr, "callarg_typecheck[cstage][collide_same_shape]: "
|
||||
"built ok, expected cstage to reject the same-leaf "
|
||||
"collision (#37 cs-side pin)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "callarg_typecheck: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("callarg_typecheck: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user