From 63e837e8209913ef123450c7894a1931dd83c42f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 02:58:09 +0900 Subject: [PATCH] wcc/ww: scopelookuptype prefers the current module's symbol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A type lookup in a bundled build resolved to the newest-installed same-leaf symbol from ANY module; prefer the current module first (mirror cstage sym.c:131; the prior attempt's failure was its own u64-vs-i32 guard bug, not a deeper layer — probe-proven). Also adds the rule-7 #58 notes at the latent varianterr/scruttype pair and rewrites the stale deferral block to closing cites. Report item [5]. --- Makefile | 11 ++ lib/ww/sym.ww | 42 +++++-- selfhost/cmd/w6c/main.combined.ww | 66 ++++++++--- selfhost/cmd/wcc/check.ww | 24 +++- selfhost/cmd/wwdump/main.combined.ww | 66 ++++++++--- test/wcc/989_slttypepref_run.c | 167 +++++++++++++++++++++++++++ 6 files changed, 331 insertions(+), 45 deletions(-) create mode 100644 test/wcc/989_slttypepref_run.c diff --git a/Makefile b/Makefile index c8d83569..c6dc1e61 100644 --- a/Makefile +++ b/Makefile @@ -259,6 +259,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_allocalias_run \ $(BIN)/test_defdim_field_run \ $(BIN)/test_defdim_argslice_run \ + $(BIN)/test_slttypepref_run \ $(BIN)/test_gunsigned_run \ $(BIN)/test_taggedidx_run \ $(BIN)/test_fnptrcollide_run \ @@ -781,6 +782,16 @@ $(BIN)/test_defdim_argslice_run: test/wcc/989_defdim_argslice_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_slttypepref_run (#58/#50, c1): scopelookuptype prefers the current +# module's SK_TYPE when a value binding shadows a type leaf two modules both +# export. Builds+runs on BOTH driver twins (rule-10). See the test header. +$(BIN)/test_slttypepref_run: test/wcc/989_slttypepref_run.c \ + $(BIN)/ww $(BIN)/ww_ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_gunsigned_run (F7-c5, #25): a module-global unsigned ident on the # divide/shift/relational path must pick the unsigned opcode. Builds+runs on # BOTH driver twins (rule-10). CLASS-M — see the test header. diff --git a/lib/ww/sym.ww b/lib/ww/sym.ww index e449b70d..59109a4b 100644 --- a/lib/ww/sym.ww +++ b/lib/ww/sym.ww @@ -103,7 +103,7 @@ export fn scopelookup(s: *scope, name: str) *sym = { return nil; }; -// scopelookuptype — find an SK_TYPE entry by name regardless of mod. +// scopelookuptype — find an SK_TYPE entry by name, same-module preferred. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, with // an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME @@ -111,23 +111,43 @@ export fn scopelookup(s: *scope, name: str) *sym = { // SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct // declared in lib/ww/lex/tok.ww with `package lex;` while // `import tok;` registers a same-name SK_USE), the resolver needs -// the type entry regardless of its declared package — the struct's -// mod may differ from the leaf (lex/tok pair) so -// scopelookupinmodule(c, leaf, leaf) won't find it. +// the type entry — the struct's mod may differ from the leaf (lex/tok +// pair) so scopelookupinmodule(c, leaf, leaf) won't find it. // -// Mirrors the bare-vs-qualified disambiguation pattern from task #57. -export fn scopelookuptype(s: *scope, name: str) *sym = { - for (s != nil) { +// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod` +// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of +// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS, +// so chain-first = last-registered — when two modules export the same +// type leaf the bare walk silently picked the newest-installed one, +// install-order-dependent, while cstage is deterministic on cur_mod. +// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the +// kind-filtered + mod-preferring single walk); sole caller passes +// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the +// reverted attempt compared str.len as u64 — str.len is i32). +export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = { + let p: *scope = s; + for (p != nil) { let h: u64 = hashstr(name); - let bi: i32 = (h % (s.nbuckets: u64)): i32; - let b: *sym = s.buckets[bi]; + let bi: i32 = (h % (p.nbuckets: u64)): i32; + let b: *sym = p.buckets[bi]; + let fallback: *sym = nil; for (b != nil) { if (streq(b.name, name)) { - if (b.skind == skind.SK_TYPE) { return b; }; + if (b.skind == skind.SK_TYPE) { + if (mod.len > 0) { + if (b.mod.len > 0) { + if (streq(b.mod, mod)) { + return b; + }; + }; + }; + if (fallback == nil) { fallback = b; }; + }; }; b = b.hashnext; }; - s = s.parent; + if (fallback != nil) { return fallback; }; + p = p.parent; }; return nil; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 11b942a4..8357a768 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10208,7 +10208,7 @@ export fn scopelookup(s: *scope, name: str) *sym = { return nil; }; -// scopelookuptype — find an SK_TYPE entry by name regardless of mod. +// scopelookuptype — find an SK_TYPE entry by name, same-module preferred. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, with // an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME @@ -10216,23 +10216,43 @@ export fn scopelookup(s: *scope, name: str) *sym = { // SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct // declared in lib/ww/lex/tok.ww with `package lex;` while // `import tok;` registers a same-name SK_USE), the resolver needs -// the type entry regardless of its declared package — the struct's -// mod may differ from the leaf (lex/tok pair) so -// scopelookupinmodule(c, leaf, leaf) won't find it. +// the type entry — the struct's mod may differ from the leaf (lex/tok +// pair) so scopelookupinmodule(c, leaf, leaf) won't find it. // -// Mirrors the bare-vs-qualified disambiguation pattern from task #57. -export fn scopelookuptype(s: *scope, name: str) *sym = { - for (s != nil) { +// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod` +// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of +// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS, +// so chain-first = last-registered — when two modules export the same +// type leaf the bare walk silently picked the newest-installed one, +// install-order-dependent, while cstage is deterministic on cur_mod. +// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the +// kind-filtered + mod-preferring single walk); sole caller passes +// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the +// reverted attempt compared str.len as u64 — str.len is i32). +export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = { + let p: *scope = s; + for (p != nil) { let h: u64 = hashstr(name); - let bi: i32 = (h % (s.nbuckets: u64)): i32; - let b: *sym = s.buckets[bi]; + let bi: i32 = (h % (p.nbuckets: u64)): i32; + let b: *sym = p.buckets[bi]; + let fallback: *sym = nil; for (b != nil) { if (streq(b.name, name)) { - if (b.skind == skind.SK_TYPE) { return b; }; + if (b.skind == skind.SK_TYPE) { + if (mod.len > 0) { + if (b.mod.len > 0) { + if (streq(b.mod, mod)) { + return b; + }; + }; + }; + if (fallback == nil) { fallback = b; }; + }; }; b = b.hashnext; }; - s = s.parent; + if (fallback != nil) { return fallback; }; + p = p.parent; }; return nil; }; @@ -11305,9 +11325,11 @@ fn aliassym(c: *checker, n: *node) *sym = { // on the head-first bucket walk: e.g. utf8.invalid `!void` // vs strconv.invalid `!i32` resolves to whichever // registered first, driving localloadop MOVSXD/MOVQ - // divergence at 994/995. Other bare-leaf callers in this - // file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee - // leaf, L600 varianterr, L647 scruttype) tracked as #55. + // divergence at 994/995. The exprtype N_IDENT / N_DOT-callee + // + exprtypeoftry / &fn-synth bare-leaf callers now all use + // scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf + // type lookups left — varianterr (:1051) + scruttype (:1098) — + // stay mod-blind but have no reproducible divergence; #58. s = scopelookupprefer(c.cur, c.curmod, nm); // #61 A.5: bare TNAME that collides with an imported // module bareword. Two shapes hit this: @@ -11325,7 +11347,13 @@ fn aliassym(c: *checker, n: *node) *sym = { // bare-vs-qualified pattern from task #57. if (s != nil) { if (s.skind != skind.SK_TYPE) { - let sm: *sym = scopelookuptype(c.cur, nm); + // #58/#50: prefer the curmod-matching SK_TYPE. + // Two modules exporting the same type leaf (e.g. + // utf8.invalid !void vs strconv.invalid !i32) + // otherwise resolve install-order-dependent here + // when a value binding shadows the leaf; cstage + // passes c->cur_mod to scope_lookup_type (sym.c). + let sm: *sym = scopelookuptype(c.cur, c.curmod, nm); if (sm != nil) { s = sm; }; }; }; @@ -11462,6 +11490,10 @@ fn varianterr(c: *checker, v: *node) bool = { if (v == nil) { return false; }; if (v.kind == nkind.N_TBANG) { return true; }; if (v.kind == nkind.N_TNAME) { + // #58: mod-blind by leaf — latent. A same-leaf error-vs-plain + // type pair across two modules could in principle flip iserror, + // but the union's variants resolve at its declaration before + // varianterr runs, so no repro exists. Tracked: #58. let s: *sym = scopelookup(c.cur, v.str); if (s != nil) { if (s.skind == skind.SK_TYPE) { @@ -11509,6 +11541,10 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #58: mod-blind by leaf — lenient-only. Feeds match + // exhaustiveness; codegen reads the stamped n.type_, so a + // mis-resolution cannot drive a wrong binary (no repro). + // Tracked: #58. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 94f6276d..2b9e72b9 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -885,9 +885,11 @@ fn aliassym(c: *checker, n: *node) *sym = { // on the head-first bucket walk: e.g. utf8.invalid `!void` // vs strconv.invalid `!i32` resolves to whichever // registered first, driving localloadop MOVSXD/MOVQ - // divergence at 994/995. Other bare-leaf callers in this - // file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee - // leaf, L600 varianterr, L647 scruttype) tracked as #55. + // divergence at 994/995. The exprtype N_IDENT / N_DOT-callee + // + exprtypeoftry / &fn-synth bare-leaf callers now all use + // scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf + // type lookups left — varianterr (:1051) + scruttype (:1098) — + // stay mod-blind but have no reproducible divergence; #58. s = scopelookupprefer(c.cur, c.curmod, nm); // #61 A.5: bare TNAME that collides with an imported // module bareword. Two shapes hit this: @@ -905,7 +907,13 @@ fn aliassym(c: *checker, n: *node) *sym = { // bare-vs-qualified pattern from task #57. if (s != nil) { if (s.skind != skind.SK_TYPE) { - let sm: *sym = scopelookuptype(c.cur, nm); + // #58/#50: prefer the curmod-matching SK_TYPE. + // Two modules exporting the same type leaf (e.g. + // utf8.invalid !void vs strconv.invalid !i32) + // otherwise resolve install-order-dependent here + // when a value binding shadows the leaf; cstage + // passes c->cur_mod to scope_lookup_type (sym.c). + let sm: *sym = scopelookuptype(c.cur, c.curmod, nm); if (sm != nil) { s = sm; }; }; }; @@ -1042,6 +1050,10 @@ fn varianterr(c: *checker, v: *node) bool = { if (v == nil) { return false; }; if (v.kind == nkind.N_TBANG) { return true; }; if (v.kind == nkind.N_TNAME) { + // #58: mod-blind by leaf — latent. A same-leaf error-vs-plain + // type pair across two modules could in principle flip iserror, + // but the union's variants resolve at its declaration before + // varianterr runs, so no repro exists. Tracked: #58. let s: *sym = scopelookup(c.cur, v.str); if (s != nil) { if (s.skind == skind.SK_TYPE) { @@ -1089,6 +1101,10 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #58: mod-blind by leaf — lenient-only. Feeds match + // exhaustiveness; codegen reads the stamped n.type_, so a + // mis-resolution cannot drive a wrong binary (no repro). + // Tracked: #58. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 2f93dd34..27889979 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10208,7 +10208,7 @@ export fn scopelookup(s: *scope, name: str) *sym = { return nil; }; -// scopelookuptype — find an SK_TYPE entry by name regardless of mod. +// scopelookuptype — find an SK_TYPE entry by name, same-module preferred. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, with // an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME @@ -10216,23 +10216,43 @@ export fn scopelookup(s: *scope, name: str) *sym = { // SK_USE sym for a leaf that ALSO names a type (e.g. `tok` struct // declared in lib/ww/lex/tok.ww with `package lex;` while // `import tok;` registers a same-name SK_USE), the resolver needs -// the type entry regardless of its declared package — the struct's -// mod may differ from the leaf (lex/tok pair) so -// scopelookupinmodule(c, leaf, leaf) won't find it. +// the type entry — the struct's mod may differ from the leaf (lex/tok +// pair) so scopelookupinmodule(c, leaf, leaf) won't find it. // -// Mirrors the bare-vs-qualified disambiguation pattern from task #57. -export fn scopelookuptype(s: *scope, name: str) *sym = { - for (s != nil) { +// #58/#50: within each scope, Pass-1 prefers an SK_TYPE whose `sym.mod` +// matches `mod`; Pass-2 falls back to the first SK_TYPE regardless of +// mod (chain-first, the prior behavior). scopedefineinmodule PREPENDS, +// so chain-first = last-registered — when two modules export the same +// type leaf the bare walk silently picked the newest-installed one, +// install-order-dependent, while cstage is deterministic on cur_mod. +// Mirrors cstage cmd/wcc/sym.c scope_lookup_type(s, mod, name) (the +// kind-filtered + mod-preferring single walk); sole caller passes +// c.curmod. i32-correct: streq throughout, only `.len > 0` guards (the +// reverted attempt compared str.len as u64 — str.len is i32). +export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = { + let p: *scope = s; + for (p != nil) { let h: u64 = hashstr(name); - let bi: i32 = (h % (s.nbuckets: u64)): i32; - let b: *sym = s.buckets[bi]; + let bi: i32 = (h % (p.nbuckets: u64)): i32; + let b: *sym = p.buckets[bi]; + let fallback: *sym = nil; for (b != nil) { if (streq(b.name, name)) { - if (b.skind == skind.SK_TYPE) { return b; }; + if (b.skind == skind.SK_TYPE) { + if (mod.len > 0) { + if (b.mod.len > 0) { + if (streq(b.mod, mod)) { + return b; + }; + }; + }; + if (fallback == nil) { fallback = b; }; + }; }; b = b.hashnext; }; - s = s.parent; + if (fallback != nil) { return fallback; }; + p = p.parent; }; return nil; }; @@ -11305,9 +11325,11 @@ fn aliassym(c: *checker, n: *node) *sym = { // on the head-first bucket walk: e.g. utf8.invalid `!void` // vs strconv.invalid `!i32` resolves to whichever // registered first, driving localloadop MOVSXD/MOVQ - // divergence at 994/995. Other bare-leaf callers in this - // file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee - // leaf, L600 varianterr, L647 scruttype) tracked as #55. + // divergence at 994/995. The exprtype N_IDENT / N_DOT-callee + // + exprtypeoftry / &fn-synth bare-leaf callers now all use + // scopelookupprefer (the #56/#4/#11a wave). The only bare-leaf + // type lookups left — varianterr (:1051) + scruttype (:1098) — + // stay mod-blind but have no reproducible divergence; #58. s = scopelookupprefer(c.cur, c.curmod, nm); // #61 A.5: bare TNAME that collides with an imported // module bareword. Two shapes hit this: @@ -11325,7 +11347,13 @@ fn aliassym(c: *checker, n: *node) *sym = { // bare-vs-qualified pattern from task #57. if (s != nil) { if (s.skind != skind.SK_TYPE) { - let sm: *sym = scopelookuptype(c.cur, nm); + // #58/#50: prefer the curmod-matching SK_TYPE. + // Two modules exporting the same type leaf (e.g. + // utf8.invalid !void vs strconv.invalid !i32) + // otherwise resolve install-order-dependent here + // when a value binding shadows the leaf; cstage + // passes c->cur_mod to scope_lookup_type (sym.c). + let sm: *sym = scopelookuptype(c.cur, c.curmod, nm); if (sm != nil) { s = sm; }; }; }; @@ -11462,6 +11490,10 @@ fn varianterr(c: *checker, v: *node) bool = { if (v == nil) { return false; }; if (v.kind == nkind.N_TBANG) { return true; }; if (v.kind == nkind.N_TNAME) { + // #58: mod-blind by leaf — latent. A same-leaf error-vs-plain + // type pair across two modules could in principle flip iserror, + // but the union's variants resolve at its declaration before + // varianterr runs, so no repro exists. Tracked: #58. let s: *sym = scopelookup(c.cur, v.str); if (s != nil) { if (s.skind == skind.SK_TYPE) { @@ -11509,6 +11541,10 @@ fn iserrvariant(c: *checker, tagged: *node, v: *node) bool = { fn scruttype(c: *checker, e: *node) *node = { if (e == nil) { return nil; }; if (e.kind == nkind.N_IDENT) { + // #58: mod-blind by leaf — lenient-only. Feeds match + // exhaustiveness; codegen reads the stamped n.type_, so a + // mis-resolution cannot drive a wrong binary (no repro). + // Tracked: #58. let s: *sym = scopelookup(c.cur, e.str); if (s == nil) { return nil; }; if (s.decl == nil) { return nil; }; diff --git a/test/wcc/989_slttypepref_run.c b/test/wcc/989_slttypepref_run.c new file mode 100644 index 00000000..9b42d8b3 --- /dev/null +++ b/test/wcc/989_slttypepref_run.c @@ -0,0 +1,167 @@ +/* + * 989_slttypepref_run (#58/#50, c1) — scopelookuptype must prefer the + * current module's SK_TYPE when a value binding shadows a type leaf that + * two modules both export. + * + * THE BUG (wwstage only, cat-A silent wrong-binary under rc=0): the + * aliassym fallback (check.ww ~908) composes scopelookupprefer-then- + * scopelookuptype; lib/ww/sym.ww scopelookuptype had NO mod parameter and + * returned the first (newest-installed) SK_TYPE in the bucket chain. + * scopedefineinmodule PREPENDS, so chain-first = last-registered. When a + * param/local shadows the type leaf (scopelookupprefer lands on the value, + * not the SK_TYPE) and two modules export the same type leaf, wwstage + * silently resolved whichever module installed last — install-order + * dependent — while cstage passes c->cur_mod to scope_lookup_type + * (sym.c:131) and resolves the current module's type deterministically. + * THE FIX: give scopelookuptype a `mod` param and prefer the curmod- + * matching SK_TYPE (Pass-1 mod-match, Pass-2 chain-first fallback); + * the sole caller passes c.curmod. Mirror of cstage scope_lookup_type. + * + * row | shape | exit (cs==ww) + * ---------+-----------------------------------------------+-------------- + * shadow | xb.f param `invalid` shadows type `invalid`; | 8 (was ww 1) + * | xb !i64, xa !i8; size(invalid) in xb.f | + * noshadow | param renamed (no shadow), aliassym not hit | 8 (control) + * + * The shadow row was RED pre-c1 (ww resolved xa's !i8 -> exit 1, the wrong + * module's type under rc=0). The noshadow control pins the non-shadow path + * (scopelookupprefer lands on the SK_TYPE directly) so the fix can't + * perturb it. Single-file multi-package source is the sanctioned shape + * (cmd/ww/main.c). + */ +#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 row { const char *label; const char *src; int want_exit; }; + +static const struct row rows[] = { + { "shadow", + "package xb;\n" + "export type invalid = !i64;\n" + "export fn f(invalid: i32) i64 = { return size(invalid): i64; };\n" + "package xa;\n" + "export type invalid = !i8;\n" + "package main;\n" + "import xb;\n" + "import xa;\n" + "fn main() int = { return xb.f(0): int; };\n", + 8 }, + + { "noshadow", + "package xb;\n" + "export type invalid = !i64;\n" + "export fn f(x: i32) i64 = { return size(invalid): i64; };\n" + "package xa;\n" + "export type invalid = !i8;\n" + "package main;\n" + "import xb;\n" + "import xa;\n" + "fn main() int = { return xb.f(0): int; };\n", + 8 }, +}; + +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/sltp_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/sltp_%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; +} + +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]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + int have_ww = (access(wdrv, X_OK) == 0); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + int gc = run_build(cdrv, &rows[i], i); + if (gc < 0) { + fprintf(stderr, "slttypepref[cstage][%s]: build/run failed " + "(got %d)\n", rows[i].label, gc); + fail++; + continue; + } + if (gc != rows[i].want_exit) { + fprintf(stderr, "slttypepref[cstage][%s]: exit=%d want=%d\n", + rows[i].label, gc, rows[i].want_exit); + fail++; + } + if (!have_ww) { + fprintf(stderr, "slttypepref: skip wwstage (no %s)\n", wdrv); + continue; + } + int gw = run_build(wdrv, &rows[i], i); + if (gw != gc) { + fprintf(stderr, "slttypepref[%s]: cs=%d != ww=%d " + "(scopelookuptype mod-preference divergence — #58/#50 c1)\n", + rows[i].label, gc, gw); + fail++; + } + if (gw != rows[i].want_exit) { + fprintf(stderr, "slttypepref[wwstage][%s]: exit=%d want=%d\n", + rows[i].label, gw, rows[i].want_exit); + fail++; + } + } + + if (fail) { + fprintf(stderr, "slttypepref_run: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("slttypepref_run: %d/%d ok\n", total, total); + return 0; +}