test: port 839_xmod_nominal_typeeqast to corpus fixtures
839_xmod_nominal_typeeqast.c -> r839_xmod_nominal_match (run-exit 42), r839_xmod_a6_concrete (run), r839_xmod_incompat_concrete (error 'not assignable'), r839_xmod_distinct_alias (error 'ambiguous without nominal layout'). r839_xmod_nominal_match enters DATABYTEID_DIVERGED: the carrier waived byte-id for the pre-existing cross-module tagged-return spill divergence; the DIVERGED row makes it loud-on-fix instead. The typeeqast-layer discriminator itself stays with the lib byteid gate (test/byteid/libbyteid_test.ww), as the carrier documented.
This commit is contained in:
7
Makefile
7
Makefile
@@ -598,7 +598,12 @@ DATABYTEID_EXPECTED_MIN = 915
|
|||||||
# wwstage spills a wwi-decl'd tagged call-result arg through a 16B
|
# wwstage spills a wwi-decl'd tagged call-result arg through a 16B
|
||||||
# scratch pair where cstage PUSHQes AX/DX (frame 64 vs 80); runtime-
|
# scratch pair where cstage PUSHQes AX/DX (frame 64 vs 80); runtime-
|
||||||
# equivalent, sibling of the closed #211 name-keyed family.
|
# equivalent, sibling of the closed #211 name-keyed family.
|
||||||
DATABYTEID_DIVERGED = r700_strings_byteindex
|
# r839_xmod_nominal_match: latent, exposed by the 839 migration (the
|
||||||
|
# carrier waived byte-id silently) — same spill family as r700: wwstage
|
||||||
|
# routes the cross-module tagged call-result through a 16B scratch pair
|
||||||
|
# where cstage PUSHQes AX/DX (frame 16 vs 32); runtime-equivalent
|
||||||
|
# (both stages run exit 42).
|
||||||
|
DATABYTEID_DIVERGED = r700_strings_byteindex r839_xmod_nominal_match
|
||||||
$(if $(DATABYTEID_FILES),,$(error test-byteid: empty data corpus))
|
$(if $(DATABYTEID_FILES),,$(error test-byteid: empty data corpus))
|
||||||
test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
|
test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
|
||||||
@work=$$(mktemp -d "$(CURDIR)/$(DATABYTEID_DIR).XXXXXX") || exit 1; \
|
@work=$$(mktemp -d "$(CURDIR)/$(DATABYTEID_DIR).XXXXXX") || exit 1; \
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package wwfixture;
|
package wwfixture;
|
||||||
|
|
||||||
def protocolversion: i32 = 1;
|
def protocolversion: i32 = 1;
|
||||||
def corpuscount: i32 = 1669;
|
def corpuscount: i32 = 1673;
|
||||||
def errorcount: i32 = 339;
|
def errorcount: i32 = 341;
|
||||||
def compilecount: i32 = 18;
|
def compilecount: i32 = 18;
|
||||||
def runcount: i32 = 206;
|
def runcount: i32 = 207;
|
||||||
def runexitcount: i32 = 1106;
|
def runexitcount: i32 = 1107;
|
||||||
def nativecount: i32 = 3338;
|
def nativecount: i32 = 3346;
|
||||||
def corpushash: str = "2c2ef7841c6da289337a72816041bdc594fa84da18d4f1e11327db3d578e1d14";
|
def corpushash: str = "a071db5169fe671d40b72d9967caaea053b505f300e64f787f3627ca0f340f5c";
|
||||||
|
|
||||||
type directive = enum i32 {
|
type directive = enum i32 {
|
||||||
ERROR = 0,
|
ERROR = 0,
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
/*
|
|
||||||
* 839_xmod_nominal_typeeqast — B-full Layer 1 (#14): typeeqast's N_TNAME
|
|
||||||
* arm now compares by RESOLVED-decl identity (aliassym pointer-equality),
|
|
||||||
* not surface spelling. A cross-module type referenced bare inside its
|
|
||||||
* defining module (`myerr`) and qualified by a consumer (`e.myerr`) used
|
|
||||||
* to mis-compare unequal (streq), so a nominal forward / variant match was
|
|
||||||
* silently mis-identified. cstage already equates them (type.c:278
|
|
||||||
* `TY_NAMED: a == b` on resolved Type); harec equates them
|
|
||||||
* (types.c:579 `STORAGE_ALIAS: ident_equal`). ww now matches at the AST
|
|
||||||
* level via the existing #51/#53 resolver — NO type-interning layer.
|
|
||||||
*
|
|
||||||
* The headline discriminator for this fix is the lib/shlex graduation
|
|
||||||
* (989 #59.15: M_WWREJECT -> M_ID). That flip needs shlex's full
|
|
||||||
* `(size | io.error)` match pipeline and is NOT reproducible standalone,
|
|
||||||
* so the gate that actually distinguishes pre-fix surface-streq from
|
|
||||||
* post-fix nominal-aliassym is 989_lib_byteid, not this test.
|
|
||||||
*
|
|
||||||
* scenario | shape | gate
|
|
||||||
* ------------------+---------------------------------------------+------
|
|
||||||
* nominal_match | match e.res whose variants are spelled BARE | 42
|
|
||||||
* | inside e, with QUALIFIED case patterns |
|
|
||||||
* | (`case e.myerr`) — nominal cover |
|
|
||||||
* a6_concrete | concrete `e.myerr` returned into `e.res` | 0
|
|
||||||
* | (A6 concrete->tagged, bare-vs-qualified) |
|
|
||||||
* incompat_concrete | `str` into `(i64 | e.myerr)` — NO str | FAIL
|
|
||||||
* | variant: confident reject (no over-accept)|
|
|
||||||
* distinct_alias | distinct alias `other = !i64` into `e.res` | FAIL
|
|
||||||
* | — rejected at CGEN (#95 structural |
|
|
||||||
* | ambiguity: two i64-underlying variants), |
|
|
||||||
* | symmetric both stages |
|
|
||||||
*
|
|
||||||
* SCOPE / no-teeth disclosure: all four rows decide IDENTICALLY against
|
|
||||||
* the pre-fix surface-streq HEAD (nominal_match->42, a6_concrete->0, the
|
|
||||||
* two NEG rows reject) on BOTH stages — the nominal-identity change does
|
|
||||||
* not flip any of them, because each is reached through a sibling
|
|
||||||
* mechanism that already handled bare-vs-qualified: nominal_match via
|
|
||||||
* casevariantpairmatch (#205 match-cover resolve), a6_concrete via the
|
|
||||||
* concrete->tagged arm's underlying-i64 resolvealias, and distinct_alias
|
|
||||||
* via the cgen #95 ambiguity reject (the CHECKER accepts it — `other`'s
|
|
||||||
* underlying i64 matches a variant; the reject is structural, not
|
|
||||||
* nominal). So this test does NOT guard a revert of typeeqast to streq;
|
|
||||||
* that is 989 #59.15's job. What it DOES pin is that the four
|
|
||||||
* cross-module nominal accept/reject decisions stay correct AND symmetric
|
|
||||||
* across cstage/wwstage (rule-10) — a forward guard against either stage
|
|
||||||
* drifting on cross-module nominal forwards.
|
|
||||||
*
|
|
||||||
* Both stages must agree (rule-10): the POS rows build+run to the same
|
|
||||||
* exit on cstage and wwstage; the NEG rows build-FAIL on both. Byte-id of
|
|
||||||
* the emitted asm is NOT asserted here — receiving a cross-module tagged
|
|
||||||
* return and re-passing it crosses a pre-existing, unrelated cgen frame-
|
|
||||||
* layout divergence; B-full Layer 1's byte-id-neutrality is gated by
|
|
||||||
* 989_lib_byteid + 990-997 (the bootstrap forwards).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <errno.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 file { const char *name; const char *src; };
|
|
||||||
|
|
||||||
struct scenario {
|
|
||||||
const char *label;
|
|
||||||
const struct file *files; /* name==NULL terminates */
|
|
||||||
int expect_build; /* 1 = build+run to want_exit; 0 = must FAIL */
|
|
||||||
int want_exit;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- nominal_match: bare union variants, qualified case patterns ---- */
|
|
||||||
static const struct file nominal_match_files[] = {
|
|
||||||
{ "e.ww",
|
|
||||||
"package e;\n"
|
|
||||||
"\n"
|
|
||||||
"export type myerr = !i64;\n"
|
|
||||||
"export type res = (i64 | myerr);\n"
|
|
||||||
"\n"
|
|
||||||
"export fn ok(v: i64) res = { return v; };\n"
|
|
||||||
"export fn bad() res = { return (-7: myerr); };\n" },
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"\n"
|
|
||||||
"import e;\n"
|
|
||||||
"\n"
|
|
||||||
"fn unwrap(r: e.res) i64 = {\n"
|
|
||||||
" match (r) {\n"
|
|
||||||
" case let n: i64 => return n;\n"
|
|
||||||
" case let x: e.myerr => return -1;\n"
|
|
||||||
" };\n"
|
|
||||||
"};\n"
|
|
||||||
"\n"
|
|
||||||
"export fn main() i32 = {\n"
|
|
||||||
" let a = unwrap(e.ok(40));\n"
|
|
||||||
" let b = unwrap(e.bad());\n"
|
|
||||||
" if (a != 40) { return 11; };\n"
|
|
||||||
" if (b != -1) { return 12; };\n"
|
|
||||||
" return 42;\n"
|
|
||||||
"};\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- a6_concrete: concrete e.myerr into e.res return (A6) ---------- */
|
|
||||||
static const struct file a6_concrete_files[] = {
|
|
||||||
{ "e.ww",
|
|
||||||
"package e;\n"
|
|
||||||
"\n"
|
|
||||||
"export type myerr = !i64;\n"
|
|
||||||
"export type res = (i64 | myerr);\n" },
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"\n"
|
|
||||||
"import e;\n"
|
|
||||||
"\n"
|
|
||||||
"fn wrap(x: e.myerr) e.res = { return x; };\n"
|
|
||||||
"\n"
|
|
||||||
"export fn main() i32 = { return 0; };\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- incompat_concrete: str into (i64|e.myerr) — must reject ------- */
|
|
||||||
static const struct file incompat_concrete_files[] = {
|
|
||||||
{ "e.ww",
|
|
||||||
"package e;\n"
|
|
||||||
"\n"
|
|
||||||
"export type myerr = !i64;\n" },
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"\n"
|
|
||||||
"import e;\n"
|
|
||||||
"\n"
|
|
||||||
"type u = (i64 | e.myerr);\n"
|
|
||||||
"\n"
|
|
||||||
"fn bad(s: str) u = { return s; };\n"
|
|
||||||
"\n"
|
|
||||||
"export fn main() i32 = { return 0; };\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ---- distinct_alias: distinct alias over same underlying — reject -- */
|
|
||||||
static const struct file distinct_alias_files[] = {
|
|
||||||
{ "e.ww",
|
|
||||||
"package e;\n"
|
|
||||||
"\n"
|
|
||||||
"export type myerr = !i64;\n"
|
|
||||||
"export type res = (i64 | myerr);\n" },
|
|
||||||
{ "main.ww",
|
|
||||||
"package main;\n"
|
|
||||||
"\n"
|
|
||||||
"import e;\n"
|
|
||||||
"\n"
|
|
||||||
"type other = !i64;\n"
|
|
||||||
"\n"
|
|
||||||
"fn f(x: other) e.res = { return x; };\n"
|
|
||||||
"\n"
|
|
||||||
"export fn main() i32 = { return 0; };\n" },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct scenario scenarios[] = {
|
|
||||||
{ "nominal_match", nominal_match_files, 1, 42 },
|
|
||||||
{ "a6_concrete", a6_concrete_files, 1, 0 },
|
|
||||||
{ "incompat_concrete", incompat_concrete_files, 0, 0 },
|
|
||||||
{ "distinct_alias", distinct_alias_files, 0, 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
struct driver { const char *name; char path[2100]; int gated; };
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_one(const struct driver *drv, const struct scenario *sc)
|
|
||||||
{
|
|
||||||
char dir[] = "/tmp/ww839_XXXXXX";
|
|
||||||
if (mkdtemp(dir) == NULL) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: mkdtemp failed\n",
|
|
||||||
drv->name, sc->label);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char path[1024], cmd[4096];
|
|
||||||
int rc = 0;
|
|
||||||
|
|
||||||
for (int i = 0; sc->files[i].name; i++) {
|
|
||||||
snprintf(path, sizeof path, "%s/%s", dir, sc->files[i].name);
|
|
||||||
FILE *f = fopen(path, "wb");
|
|
||||||
if (!f) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: write %s\n",
|
|
||||||
drv->name, sc->label, sc->files[i].name);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
int writebad = fputs(sc->files[i].src, f) == EOF || ferror(f);
|
|
||||||
if (fclose(f) != 0) writebad = 1;
|
|
||||||
if (writebad) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: write %s\n",
|
|
||||||
drv->name, sc->label, sc->files[i].name);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "cd %s && %s build -I %s %s/main.ww "
|
|
||||||
">/dev/null 2>&1", dir, drv->path, dir, dir);
|
|
||||||
int brc = runwait(cmd);
|
|
||||||
|
|
||||||
if (!sc->expect_build) {
|
|
||||||
/* NEG: the build must FAIL on this stage. */
|
|
||||||
if (brc == 0) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: built ok, expected a "
|
|
||||||
"confident reject\n", drv->name, sc->label);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (brc != 0) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: build failed, expected ok\n",
|
|
||||||
drv->name, sc->label);
|
|
||||||
rc = -1; goto done;
|
|
||||||
}
|
|
||||||
snprintf(path, sizeof path, "%s/main", dir);
|
|
||||||
int got = runwait(path);
|
|
||||||
if (got != sc->want_exit) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: exit %d, want %d\n",
|
|
||||||
drv->name, sc->label, got, sc->want_exit);
|
|
||||||
rc = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
done:
|
|
||||||
{
|
|
||||||
int cleanfail = 0;
|
|
||||||
for (int i = 0; sc->files[i].name; i++) {
|
|
||||||
snprintf(path, sizeof path, "%s/%s", dir,
|
|
||||||
sc->files[i].name);
|
|
||||||
if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
}
|
|
||||||
snprintf(path, sizeof path, "%s/main", dir);
|
|
||||||
if (unlink(path) != 0 && errno != ENOENT) cleanfail = 1;
|
|
||||||
snprintf(path, sizeof path, "%s/main.sepwork", dir);
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", path);
|
|
||||||
if (runwait(cmd) != 0) cleanfail = 1;
|
|
||||||
if (rmdir(dir) != 0) cleanfail = 1;
|
|
||||||
if (cleanfail) {
|
|
||||||
fprintf(stderr, "839[%s][%s]: workspace cleanup failed\n",
|
|
||||||
drv->name, sc->label);
|
|
||||||
if (rc == 0) rc = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main(void)
|
|
||||||
{
|
|
||||||
const char *bin = getenv("BIN");
|
|
||||||
if (!bin) bin = "out/bin";
|
|
||||||
char absbin[2048];
|
|
||||||
if (bin[0] != '/') {
|
|
||||||
char cwd[1024];
|
|
||||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
||||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
|
||||||
bin = absbin;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct driver drivers[2];
|
|
||||||
snprintf(drivers[0].path, sizeof drivers[0].path, "%s/ww", bin);
|
|
||||||
drivers[0].name = "cstage";
|
|
||||||
drivers[0].gated = 0;
|
|
||||||
snprintf(drivers[1].path, sizeof drivers[1].path, "%s/ww_ww", bin);
|
|
||||||
drivers[1].name = "wwstage";
|
|
||||||
drivers[1].gated = 1;
|
|
||||||
|
|
||||||
int n = (int)(sizeof scenarios / sizeof scenarios[0]);
|
|
||||||
int total = 0, fail = 0;
|
|
||||||
|
|
||||||
for (int d = 0; d < 2; d++) {
|
|
||||||
if (drivers[d].gated && access(drivers[d].path, X_OK) != 0) {
|
|
||||||
fprintf(stderr, "839: skip %s (no %s)\n",
|
|
||||||
drivers[d].name, drivers[d].path);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
total++;
|
|
||||||
if (run_one(&drivers[d], &scenarios[i]) != 0)
|
|
||||||
fail++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr, "839 xmod_nominal_typeeqast: %d/%d failed\n",
|
|
||||||
fail, total);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("xmod_nominal_typeeqast: %d/%d ok (cross-module nominal "
|
|
||||||
"accept + reject, both stages)\n", total, total);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
9
test/wcc/data/r839_xmod_a6_concrete/case.ww
Normal file
9
test/wcc/data/r839_xmod_a6_concrete/case.ww
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//ww:run
|
||||||
|
// migrated from test/wcc/839_xmod_nominal_typeeqast.c: A6 — concrete e.myerr returned into e.res (bare-vs-qualified nominal forward), both stages accept.
|
||||||
|
package e;
|
||||||
|
export type myerr = !i64;
|
||||||
|
export type res = (i64 | myerr);
|
||||||
|
package main;
|
||||||
|
import e;
|
||||||
|
fn wrap(x: e.myerr) e.res = { return x; };
|
||||||
|
export fn main() i32 = { return 0; };
|
||||||
10
test/wcc/data/r839_xmod_distinct_alias/case.ww
Normal file
10
test/wcc/data/r839_xmod_distinct_alias/case.ww
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//ww:error "ambiguous without nominal layout"
|
||||||
|
// migrated from test/wcc/839_xmod_nominal_typeeqast.c: a distinct local alias over the same underlying i64 into e.res — #95 structural-ambiguity reject, both stages.
|
||||||
|
package e;
|
||||||
|
export type myerr = !i64;
|
||||||
|
export type res = (i64 | myerr);
|
||||||
|
package main;
|
||||||
|
import e;
|
||||||
|
type other = !i64;
|
||||||
|
fn f(x: other) e.res = { return x; };
|
||||||
|
export fn main() i32 = { return 0; };
|
||||||
9
test/wcc/data/r839_xmod_incompat_concrete/case.ww
Normal file
9
test/wcc/data/r839_xmod_incompat_concrete/case.ww
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//ww:error "not assignable"
|
||||||
|
// migrated from test/wcc/839_xmod_nominal_typeeqast.c: str into (i64 | e.myerr) has no variant — confident reject, both stages.
|
||||||
|
package e;
|
||||||
|
export type myerr = !i64;
|
||||||
|
package main;
|
||||||
|
import e;
|
||||||
|
type u = (i64 | e.myerr);
|
||||||
|
fn bad(s: str) u = { return s; };
|
||||||
|
export fn main() i32 = { return 0; };
|
||||||
22
test/wcc/data/r839_xmod_nominal_match/case.ww
Normal file
22
test/wcc/data/r839_xmod_nominal_match/case.ww
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//ww:run-exit 42
|
||||||
|
// migrated from test/wcc/839_xmod_nominal_typeeqast.c: bare-spelled union variants matched via QUALIFIED case patterns (`case let x: e.myerr`) — cross-module nominal cover.
|
||||||
|
package e;
|
||||||
|
export type myerr = !i64;
|
||||||
|
export type res = (i64 | myerr);
|
||||||
|
export fn ok(v: i64) res = { return v; };
|
||||||
|
export fn bad() res = { return (-7: myerr); };
|
||||||
|
package main;
|
||||||
|
import e;
|
||||||
|
fn unwrap(r: e.res) i64 = {
|
||||||
|
match (r) {
|
||||||
|
case let n: i64 => return n;
|
||||||
|
case let x: e.myerr => return -1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export fn main() i32 = {
|
||||||
|
let a = unwrap(e.ok(40));
|
||||||
|
let b = unwrap(e.bad());
|
||||||
|
if (a != 40) { return 11; };
|
||||||
|
if (b != -1) { return 12; };
|
||||||
|
return 42;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user