From 0546bda6c450325a09a891b120b6560f406842e3 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 27 May 2026 09:18:03 +0900 Subject: [PATCH] wcc: array-init accept-if-fits coercion (#130, merges #146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align bare-int array-init assignability to Hare's literal-fits rule (ref/harec/src/types.c promote_flexible): accept untyped-int array elements that FIT the element type, reject out-of-range loud. cstage (rejected all bare-int arrays, over-strict) and wwstage (accepted + silently truncated out-of-range, over-loose) converge to the same accept-if-fits rule. Per-element: foldable int literal range-checked against element type [min,max] via def_cast_fits (rule-13 type-table widths); non-foldable element falls back to type_assignable. cstage: new arrlit_init_fits, N_LET decl-check fallback after whole- array type_assignable fails. wwstage: checkletassign array branch + route top-level lets through checkletassign (were unchecked — only function-body lets ran assignability; closes #146 wwstage str->u8 over-accept). Scalar-init range-check (let X:u8=300 truncates, both stages, pre-existing) deferred to #148 — language-wide, needs bootstrap audit + explicit-cast conversion. def-array accept-if-fits deferred to #151 (def constfold machinery, different risk). Both bootstrap-NEUTRAL. Test 920 (14 rows — accept: in-range u8/u32/u64/i32 + u8/i8 boundary + typed regression + non-foldable-body; reject: over-range + over-256 + i8-over + neg-for-unsigned + str->u8 + non-foldable-wider). Non- foldable else-branch cs==ww verified (matching-type accept + byte-id; wider-runtime-int reject both stages). Make test: 183/183 incl 990-997 byte-id + combined_ww_fresh. --- Makefile | 6 + cmd/wcc/check.c | 46 +++- selfhost/cmd/w6c/main.combined.ww | 67 +++++ selfhost/cmd/wcc/check.ww | 67 +++++ selfhost/cmd/wwdump/main.combined.ww | 67 +++++ test/wcc/920_array_init_acceptiffits_run.c | 285 +++++++++++++++++++++ 6 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 test/wcc/920_array_init_acceptiffits_run.c diff --git a/Makefile b/Makefile index 95facd41..50273025 100644 --- a/Makefile +++ b/Makefile @@ -342,6 +342,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_def_float_lit_run \ $(BIN)/test_struct_composite_init_run \ $(BIN)/test_array_static_init_run \ + $(BIN)/test_array_init_acceptiffits_run \ $(BIN)/test_f64cgen_run \ $(BIN)/test_f64crossmod_run \ $(BIN)/test_tuprecv_run \ @@ -1165,6 +1166,11 @@ $(BIN)/test_array_static_init_run: test/wcc/919_array_static_init_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_array_init_acceptiffits_run: test/wcc/920_array_init_acceptiffits_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_f64cgen_run: test/wcc/951_f64cgen_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index d3f44fdf..7b42dc72 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -322,6 +322,49 @@ def_cast_fits(Type *t, u64 v) return ext == v; } +/* arrlit_init_fits — #130: accept-if-fits for `let/def A: [N]T = [..]` + * where the whole-array type_assignable failed (bare-int elements + * synthesize [N]i32 via type_default, losing the literal flavor that + * the scalar coercion rule honours). Per element: + * - foldable int literal → range-check against T via def_cast_fits. + * In-range accepts; out-of-range REJECTS loud (rule-7 / Drew: + * Hare range-checks at literal-value level, ref/harec types.c:923 + * promote_flexible — never a silent truncate). + * - non-foldable element → type_assignable(T, elem->type), reusing + * the same coercion rule the scalar path uses (untyped-int→u8 ok, + * str→u8 not). + * Returns 1 iff every element fits; the caller only consults this + * after type_assignable already said no, so a 0 means a genuine + * reject. Scoped to the ARRAY path — scalar overflow stays a separate + * language-wide gap (#148). */ +static int +arrlit_init_fits(Checker *c, Type *dt, Node *rhs) +{ + if (rhs == NULL || rhs->kind != N_ARRLIT) return 0; + Type *u = (dt && dt->kind == TY_NAMED) ? dt->under : dt; + if (u == NULL || u->kind != TY_ARRAY) return 0; + Type *et = u->sub; + Type *eu = (et && et->kind == TY_NAMED) ? et->under : et; + for (Node *e = rhs->list; e; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) + continue; + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + u64 v; + if (ev && type_isint(eu) && fold_int_literal(ev, &v)) { + if (!def_cast_fits(eu, v)) { + err(c, e->pos, "array element out of range " + "for %s", type_name(c->a, et)); + return 0; + } + continue; + } + if (!type_assignable(et, e->type)) return 0; + } + return 1; +} + /* eval_def_const — fold a top-level def's rhs to a u64 constant, * resolving sibling and imported def references, casts, and * arithmetic (#88). Reuses the shared fold_int_literal leaf/unary @@ -2355,7 +2398,8 @@ check_file(Checker *c, Node *file) Type *rt = cexpr(c, d->rhs); if (d->type == NULL) d->type = type_default(rt); if (d->type && rt != ty_err && d->type != ty_err - && !type_assignable(d->type, rt)) + && !type_assignable(d->type, rt) + && !arrlit_init_fits(c, d->type, d->rhs)) err(c, d->pos, "let %s init not assignable", d->str); } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e81d7a77..4b299780 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10673,6 +10673,66 @@ fn checkletassign(c: *checker, n: *node) void = { }; }; }; + // #130: array-init accept-if-fits. When lhs is [N]T and rhs is an + // array literal, per-element check: foldable int literal → + // defcastfits range-check (reject out-of-range loud, rule-7/Drew — + // Hare range-checks at literal-value level); non-foldable → + // isassignable to the element type. This BOTH accepts in-range + // bare-int (the #130 headline, matching cstage) AND closes the + // wwstage over-accept where str→u8 / out-of-range silently passed + // (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped + // to the array path; scalar-init range-check is a separate + // language-wide gap (#148). + if (n.lhs.kind == nkind.N_TARRAY) { + if (n.rhs.kind == nkind.N_ARRLIT) { + let elemtn: *node = n.lhs.lhs; + let at: *tinfo = tinfofornode(c, n.lhs); + let et: *tinfo = nil; + if (at != nil) { et = at.sub; }; + for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; }; + let e: *node = n.rhs.list; + for (e != nil) { + let skip: bool = false; + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { skip = true; }; + }; + if (!skip) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let v: u64 = 0u64; + let folded: bool = false; + if (et != nil) { + if (typeisint(et)) { + if (ev != nil) { + folded = foldintliteral(ev, &v); + }; + }; + }; + if (folded) { + if (!defcastfits(et, v)) { + let m: str = "let: array element out of range\n"; + os.write(2, m.ptr, m.len: u64); + c.errs += 1; + return; + }; + } else { + let est: *node = exprtype(c, ev, elemtn); + let conf2: bool = false; + if (est != nil) { + if (!isassignable(c, elemtn, est, &conf2)) { + if (conf2) { + errnotassign(c, elemtn, est, "let"); + return; + }; + }; + }; + }; + }; + e = e.next; + }; + return; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; @@ -11052,6 +11112,13 @@ export fn checkfile(c: *checker, file: *node) void = { } else { if (k == nkind.N_LET) { if (d.lhs != nil) { resolvewalk(c, d.lhs); }; if (d.rhs != nil) { resolvewalk(c, d.rhs); }; + // #130: top-level let assignability — the subtree + // resolvewalk above stamps types but never runs the + // init-assignability check (function-body lets get it + // via resolvewalk's post-order L247; top-level lets + // were missed). Needed for the array accept-if-fits + // range-check to fire on module-level `let A:[N]u8=[..]`. + checkletassign(c, d); };};};}; d = d.next; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 3c670a2c..78a660a4 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -3104,6 +3104,66 @@ fn checkletassign(c: *checker, n: *node) void = { }; }; }; + // #130: array-init accept-if-fits. When lhs is [N]T and rhs is an + // array literal, per-element check: foldable int literal → + // defcastfits range-check (reject out-of-range loud, rule-7/Drew — + // Hare range-checks at literal-value level); non-foldable → + // isassignable to the element type. This BOTH accepts in-range + // bare-int (the #130 headline, matching cstage) AND closes the + // wwstage over-accept where str→u8 / out-of-range silently passed + // (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped + // to the array path; scalar-init range-check is a separate + // language-wide gap (#148). + if (n.lhs.kind == nkind.N_TARRAY) { + if (n.rhs.kind == nkind.N_ARRLIT) { + let elemtn: *node = n.lhs.lhs; + let at: *tinfo = tinfofornode(c, n.lhs); + let et: *tinfo = nil; + if (at != nil) { et = at.sub; }; + for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; }; + let e: *node = n.rhs.list; + for (e != nil) { + let skip: bool = false; + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { skip = true; }; + }; + if (!skip) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let v: u64 = 0u64; + let folded: bool = false; + if (et != nil) { + if (typeisint(et)) { + if (ev != nil) { + folded = foldintliteral(ev, &v); + }; + }; + }; + if (folded) { + if (!defcastfits(et, v)) { + let m: str = "let: array element out of range\n"; + os.write(2, m.ptr, m.len: u64); + c.errs += 1; + return; + }; + } else { + let est: *node = exprtype(c, ev, elemtn); + let conf2: bool = false; + if (est != nil) { + if (!isassignable(c, elemtn, est, &conf2)) { + if (conf2) { + errnotassign(c, elemtn, est, "let"); + return; + }; + }; + }; + }; + }; + e = e.next; + }; + return; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; @@ -3483,6 +3543,13 @@ export fn checkfile(c: *checker, file: *node) void = { } else { if (k == nkind.N_LET) { if (d.lhs != nil) { resolvewalk(c, d.lhs); }; if (d.rhs != nil) { resolvewalk(c, d.rhs); }; + // #130: top-level let assignability — the subtree + // resolvewalk above stamps types but never runs the + // init-assignability check (function-body lets get it + // via resolvewalk's post-order L247; top-level lets + // were missed). Needed for the array accept-if-fits + // range-check to fire on module-level `let A:[N]u8=[..]`. + checkletassign(c, d); };};};}; d = d.next; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 996ecb0a..97bb874d 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10673,6 +10673,66 @@ fn checkletassign(c: *checker, n: *node) void = { }; }; }; + // #130: array-init accept-if-fits. When lhs is [N]T and rhs is an + // array literal, per-element check: foldable int literal → + // defcastfits range-check (reject out-of-range loud, rule-7/Drew — + // Hare range-checks at literal-value level); non-foldable → + // isassignable to the element type. This BOTH accepts in-range + // bare-int (the #130 headline, matching cstage) AND closes the + // wwstage over-accept where str→u8 / out-of-range silently passed + // (#146 merged). Mirrors cstage check.c arrlit_init_fits. Scoped + // to the array path; scalar-init range-check is a separate + // language-wide gap (#148). + if (n.lhs.kind == nkind.N_TARRAY) { + if (n.rhs.kind == nkind.N_ARRLIT) { + let elemtn: *node = n.lhs.lhs; + let at: *tinfo = tinfofornode(c, n.lhs); + let et: *tinfo = nil; + if (at != nil) { et = at.sub; }; + for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; }; + let e: *node = n.rhs.list; + for (e != nil) { + let skip: bool = false; + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { skip = true; }; + }; + if (!skip) { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let v: u64 = 0u64; + let folded: bool = false; + if (et != nil) { + if (typeisint(et)) { + if (ev != nil) { + folded = foldintliteral(ev, &v); + }; + }; + }; + if (folded) { + if (!defcastfits(et, v)) { + let m: str = "let: array element out of range\n"; + os.write(2, m.ptr, m.len: u64); + c.errs += 1; + return; + }; + } else { + let est: *node = exprtype(c, ev, elemtn); + let conf2: bool = false; + if (est != nil) { + if (!isassignable(c, elemtn, est, &conf2)) { + if (conf2) { + errnotassign(c, elemtn, est, "let"); + return; + }; + }; + }; + }; + }; + e = e.next; + }; + return; + }; + }; let conf: bool = false; let ok: bool = isassignable(c, n.lhs, src, &conf); if (!conf) { return; }; @@ -11052,6 +11112,13 @@ export fn checkfile(c: *checker, file: *node) void = { } else { if (k == nkind.N_LET) { if (d.lhs != nil) { resolvewalk(c, d.lhs); }; if (d.rhs != nil) { resolvewalk(c, d.rhs); }; + // #130: top-level let assignability — the subtree + // resolvewalk above stamps types but never runs the + // init-assignability check (function-body lets get it + // via resolvewalk's post-order L247; top-level lets + // were missed). Needed for the array accept-if-fits + // range-check to fire on module-level `let A:[N]u8=[..]`. + checkletassign(c, d); };};};}; d = d.next; }; diff --git a/test/wcc/920_array_init_acceptiffits_run.c b/test/wcc/920_array_init_acceptiffits_run.c new file mode 100644 index 00000000..b531a13c --- /dev/null +++ b/test/wcc/920_array_init_acceptiffits_run.c @@ -0,0 +1,285 @@ +/* + * 920_array_init_acceptiffits_run — cs==ww net for #130: module-level + * `let A: [N]T = [...]` array-init "accept-if-fits". + * + * Pre-#130 divergence (Drew-adjudicated B/E synthesis): + * - cstage REJECTED all bare-int array elements ("init not assignable") + * — its array assignability path didn't wire the untyped-int→narrow- + * element coercion its OWN scalar path has. + * - wwstage ACCEPTED everything (no per-element validation): bare-int + * in-range (correct), bare-int out-of-range (silent truncate — + * miscompile), str→u8 (silent garbage). Top-level lets weren't even + * run through checkletassign. + * + * #130 fix = accept-if-fits, BOTH stages converge: + * - foldable int literal element → range-check against T via + * def_cast_fits/defcastfits (type table, rule 13). In-range accepts; + * out-of-range REJECTS loud (rule-7 / Drew: Hare range-checks at the + * literal-value level, ref/harec/src/types.c:923 promote_flexible). + * - non-foldable element → type_assignable / isassignable to T + * (untyped-int→u8 ok; str→u8 rejected). + * - cstage: check.c arrlit_init_fits fallback after whole-array + * type_assignable fails. + * - wwstage: check.ww checkletassign array branch + top-level lets now + * routed through checkletassign (were missed). Merges #146 (the + * wwstage str→u8 over-accept). + * + * Scope: ARRAY-init only. Scalar `let X: u8 = 300` truncation is a + * pre-existing language-wide gap (#148), deferred. + * + * This test asserts the ACCEPT rows compile + run + cs==ww byte-id, and + * (via the cstage/wwstage exit-code probe) that the REJECT rows fail to + * build on BOTH stages. Reject rows use a separate build-must-fail check. + * + * Accept rows (size strata 1B/4B/8B, sign, typed-vs-bare, both stages): + * - bare_inrange `[4]u8 = [1,2,3,4]` → A[0]==1 + * - bare_u32 `[4]u32 = [10,20,30,40]` → A[0]==10 + * - bare_u64 `[2]u64 = [5,6]` → A[0]==5 + * - signed_inrange `[4]i32 = [-1,-2,-3,-4]` → A[0]==-1 (255) + * - typed_regress `[4]u8 = [1u8,2u8,3u8,4u8]` → A[0]==1 + * - boundary_max `[2]u8 = [255, 0]` → A[0]==255 + * + * Reject rows (must FAIL build on both stages): + * - over_range `[2]u8 = [300, 1]` + * - neg_for_unsigned `[2]u8 = [-1, 0]` + * - str_to_u8 `[2]u8 = ["x", "y"]` + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct arow { const char *label; const char *src; int want_exit; }; +struct rrow { const char *label; const char *src; }; + +static const struct arow accept_rows[] = { + { "bare_inrange", + "package main;\n" + "let A: [4]u8 = [1, 2, 3, 4];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 1 }, + { "bare_u32", + "package main;\n" + "let A: [4]u32 = [10, 20, 30, 40];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 10 }, + { "bare_u64", + "package main;\n" + "let A: [2]u64 = [5, 6];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 5 }, + { "signed_inrange", + "package main;\n" + "let A: [4]i32 = [-1, -2, -3, -4];\n" + "export fn main() i32 = { return A[0]; };\n", 255 /* -1 */ }, + { "typed_regress", + "package main;\n" + "let A: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 1 }, + { "boundary_max", + "package main;\n" + "let A: [2]u8 = [255, 0];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 255 }, + /* i8 signed boundary: -128..127 both in-range. */ + { "i8_boundary", + "package main;\n" + "let A: [2]i8 = [127, -128];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 127 }, + /* Non-foldable element of matching type — exercises the else + * branch (type_assignable / isassignable, not the fold path). + * Must be a FUNCTION-BODY array: a module-level array with a + * non-foldable (runtime-valued) element isn't statically + * emittable (no const fold → no DATA row). The checker else + * branch fires identically for body lets, where the stack slot + * takes the runtime value. cs==ww verified. */ + { "nonfold_match", + "package main;\n" + "export fn main() i32 = {\n" + " let u: u8 = 5u8;\n" + " let A: [2]u8 = [u, 0u8];\n" + " return A[0]: i32;\n" + "};\n", 5 }, + { NULL, NULL, 0 } +}; + +static const struct rrow reject_rows[] = { + { "over_range", + "package main;\n" + "let A: [2]u8 = [300, 1];\n" + "export fn main() i32 = { return A[0]: i32; };\n" }, + { "neg_for_unsigned", + "package main;\n" + "let A: [2]u8 = [-1, 0];\n" + "export fn main() i32 = { return 0; };\n" }, + { "str_to_u8", + "package main;\n" + "let A: [2]u8 = [\"x\", \"y\"];\n" + "export fn main() i32 = { return 0; };\n" }, + /* Just-over-256 — pins the u8 upper bound exactly. */ + { "over_256", + "package main;\n" + "let A: [2]u8 = [256, 0];\n" + "export fn main() i32 = { return 0; };\n" }, + /* i8 over-range (128 > 127). */ + { "i8_over", + "package main;\n" + "let A: [2]i8 = [128, 0];\n" + "export fn main() i32 = { return 0; };\n" }, + /* Non-foldable wider-runtime int → narrow element needs an + * explicit cast in Hare; reject both stages (the else-branch + * reject path, cs==ww verified). */ + { "nonfold_wider", + "package main;\n" + "let i: int = 5;\n" + "let A: [2]u8 = [i, 0u8];\n" + "export fn main() i32 = { return 0; };\n" }, + { NULL, NULL } +}; + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa); + int cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +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 w6c[1100], w6c_ww[1100]; + snprintf(w6c, sizeof w6c, "%s/w6c", bin); + snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); + if (access(w6c_ww, X_OK) != 0) { + fprintf(stderr, "arrfit: w6c_ww missing — cannot run the " + "cs==ww gate (the whole point of this test)\n"); + return 1; + } + + int n = 0, fail = 0; + + /* Accept rows: cstage build + run + cs==ww byte-id. */ + for (int i = 0; accept_rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwafit_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(accept_rows[i].src, f); + fclose(f); + + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwafit_%d_d_%d", + getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", + tmpdir, bin, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "accept[%s]: cstage build failed\n", + accept_rows[i].label); + fail++; unlink(src); rmdir(tmpdir); continue; + } + char outbin[128]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + if (got != accept_rows[i].want_exit) { + fprintf(stderr, "accept[%s]: exit %d, want %d\n", + accept_rows[i].label, got, accept_rows[i].want_exit); + fail++; + } + unlink(outbin); rmdir(tmpdir); + + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwafit_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwafit_%d_%d_ww.s", + getpid(), i); + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "accept[%s]: w6c failed\n", accept_rows[i].label); + fail++; unlink(src); continue; + } + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, ws_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "accept[%s]: w6c_ww failed\n", accept_rows[i].label); + fail++; unlink(src); unlink(cs_s); continue; + } + if (slurp_eq(cs_s, ws_s) != 0) { + fprintf(stderr, "accept[%s]: cs/ww .s DIFFER (rule-10)\n", + accept_rows[i].label); + fail++; + } + unlink(src); unlink(cs_s); unlink(ws_s); + } + + /* Reject rows: BOTH stages must fail to build (loud reject). */ + for (int i = 0; reject_rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwrfit_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(reject_rows[i].src, f); + fclose(f); + + char cmd[2048], dst[80]; + snprintf(dst, sizeof dst, "/tmp/wwrfit_%d_%d.s", getpid(), i); + + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, dst, src); + int cs_rc = runwait(cmd); + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c_ww, dst, src); + int ww_rc = runwait(cmd); + + if (cs_rc == 0) { + fprintf(stderr, "reject[%s]: cstage ACCEPTED (want reject)\n", + reject_rows[i].label); + fail++; + } + if (ww_rc == 0) { + fprintf(stderr, "reject[%s]: wwstage ACCEPTED (want reject)\n", + reject_rows[i].label); + fail++; + } + unlink(src); unlink(dst); + } + + if (fail) { + fprintf(stderr, "%d/%d array-init-accept-if-fits tests failed\n", + fail, n); + return 1; + } + printf("arrfit: %d/%d ok (accept: run + cs==ww; reject: both-stage fail)\n", + n, n); + return 0; +}