/* * 950_selfcheck — selfhost typechecker emits the same errors the * C-side checker does for tagged-union misuse. * * Each row is a .ww fixture + an expected stderr substring. We run * `wwdump_ww -r` (which invokes the selfhost checker's resolve + * typed passes) and grep for the substring. */ #include #include #include #include #include static int runcap(const char *cmd, char **outerr, size_t *outlen) { char tmp[256]; snprintf(tmp, sizeof tmp, "/tmp/wwd_sc_%d", getpid()); char full[2048]; snprintf(full, sizeof full, "%s 2>%s 1>/dev/null", cmd, tmp); int rc = system(full); FILE *f = fopen(tmp, "rb"); if (!f) { unlink(tmp); return -1; } fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET); char *b = malloc((size_t)n + 1); if (!b) { fclose(f); unlink(tmp); return -1; } if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); unlink(tmp); return -1; } b[n] = '\0'; fclose(f); unlink(tmp); *outerr = b; *outlen = (size_t)n; (void)rc; return 0; } static int writefile(const char *path, const char *src) { FILE *f = fopen(path, "wb"); if (!f) return -1; fputs(src, f); fclose(f); return 0; } struct row { const char *src; const char *expect; }; static const struct row rows[] = { /* match: missing variant */ { "fn pick() (i32 | str | bool) = { return 1; };\n" "fn caller() void = {\n" " let v: (i32 | str | bool) = pick();\n" " match (v) {\n" " case let x: i32 => { };\n" " case let x: str => { };\n" " };\n" "};\n", "match: variant not handled" }, /* ?: enclosing not tagged */ { "fn inner() (i32 | str) = { return 1; };\n" "fn caller() i32 = {\n" " let v: i32 = inner()?;\n" " return v;\n" "};\n", "?: enclosing fn return is not tagged" }, /* ?: error variant not in enclosing return */ { "fn inner() (i32 | str | bool) = { return 1; };\n" "fn caller() (i32 | str) = {\n" " let v: i32 = inner()?;\n" " return v;\n" "};\n", "?: error variant not in enclosing return" }, /* !-flag aware: legal case (one success, two errors propagated) */ { "type invalid = !i32;\n" "type overflow = !void;\n" "fn parse() (i64 | invalid | overflow) = { return 0i64; };\n" "fn caller() (i64 | invalid | overflow) = {\n" " let v: i64 = parse()?;\n" " return v + 1;\n" "};\n", NULL }, /* expect no error */ /* !-flag aware: missing one error variant in enclosing return */ { "type invalid = !i32;\n" "type overflow = !void;\n" "fn parse() (i64 | invalid | overflow) = { return 0i64; };\n" "fn caller() (i64 | invalid) = {\n" " let v: i64 = parse()?;\n" " return v + 1;\n" "};\n", "?: error variant not in enclosing return" }, /* case T => names a non-variant */ { "fn pick() (i32 | str) = { return 1; };\n" "fn caller() void = {\n" " let v: (i32 | str) = pick();\n" " match (v) {\n" " case let n: i32 => { };\n" " case let s: str => { };\n" " case let f: f64 => { };\n" " };\n" "};\n", "case: not a variant" }, /* is T where T isn't a variant of the operand */ { "fn pick() (i32 | str) = { return 1; };\n" "fn caller() void = {\n" " let v: (i32 | str) = pick();\n" " if (v is f64) { };\n" "};\n", "is/as: not a variant" }, /* let init-type mismatch on primitives */ { "fn caller() void = {\n" " let x: bool = 42;\n" "};\n", "let: not assignable" }, /* return type mismatch */ { "fn caller() i32 = {\n" " return \"hi\";\n" "};\n", "return: not assignable" }, }; int main(void) { const char *bin = getenv("BIN"); if (!bin) bin = "out/bin"; char wwdump[2048]; snprintf(wwdump, sizeof wwdump, "%s/wwdump_ww", bin); int n = sizeof rows / sizeof rows[0]; int fail = 0; for (int i = 0; i < n; i++) { char path[64]; snprintf(path, sizeof path, "/tmp/wwd_sc_%d_%d.ww", getpid(), i); if (writefile(path, rows[i].src) != 0) { fail++; continue; } char cmd[2048]; snprintf(cmd, sizeof cmd, "%s -r %s", wwdump, path); char *err = NULL; size_t elen = 0; runcap(cmd, &err, &elen); const char *want = rows[i].expect; int got_match = (err && want && strstr(err, want) != NULL); int expected_no_err = (want == NULL); int err_present = err && strstr(err, "match: variant not handled") != NULL; err_present = err_present || (err && strstr(err, "?: error variant not in enclosing return") != NULL); err_present = err_present || (err && strstr(err, "?: enclosing fn return is not tagged") != NULL); err_present = err_present || (err && strstr(err, "?: enclosing fn has no tagged-union return") != NULL); err_present = err_present || (err && strstr(err, "case: not a variant") != NULL); err_present = err_present || (err && strstr(err, "is/as: not a variant") != NULL); err_present = err_present || (err && strstr(err, "is/as: operand is not a tagged union") != NULL); err_present = err_present || (err && strstr(err, "let: not assignable") != NULL); err_present = err_present || (err && strstr(err, "return: not assignable") != NULL); int ok; if (expected_no_err) ok = !err_present; else ok = got_match; if (!ok) { fprintf(stderr, "row %d failed: expected %s, got stderr:\n%s\n", i, want ? want : "(no error)", err ? err : "(empty)"); fail++; } free(err); unlink(path); } if (fail) { fprintf(stderr, "%d/%d selfcheck rows failed\n", fail, n); return 1; } printf("selfcheck: %d/%d ok\n", n, n); return 0; }