test: port 696/697/699 resolver carriers to corpus fixtures
696_modtype_leaf_collision.c -> r696_modtype_leaf_pos (run-exit 26),
r696_modtype_leaf_neg (staged error; w6c 'no field', w6c_ww
asserttyped: dot — the old no-wwstage-checkfile comment is stale)
697_samemod_prefer.c -> r697_samemod_prefer (compile)
699_use_promote_alias.c -> r699_usepromote_{type,fn,def,var}
(run-exit 42 each)
Single-file multi-package form folds the modcollision/samemodprefer/
usepromote data trees into the fixtures; the trees retire with their
carriers. The dir-tree .wwi transport of colliding type decls is owned
by test/byteid/wwi_test.ww. Coverage upgrades: 697 and the 699 rows
gain the wwstage leg the carriers gated or skipped, and all positive
rows enter the blanket test-data-byteid net.
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
package wwfixture;
|
package wwfixture;
|
||||||
|
|
||||||
def protocolversion: i32 = 1;
|
def protocolversion: i32 = 1;
|
||||||
def corpuscount: i32 = 1634;
|
def corpuscount: i32 = 1641;
|
||||||
def errorcount: i32 = 336;
|
def errorcount: i32 = 337;
|
||||||
def compilecount: i32 = 17;
|
def compilecount: i32 = 18;
|
||||||
def runcount: i32 = 198;
|
def runcount: i32 = 198;
|
||||||
def runexitcount: i32 = 1083;
|
def runexitcount: i32 = 1088;
|
||||||
def nativecount: i32 = 3268;
|
def nativecount: i32 = 3282;
|
||||||
def corpushash: str = "cb7cf09decc2f64b40c1f06b836c5b2aec0a4f1b9b2a78665f228606a0245a1f";
|
def corpushash: str = "d518a094a11081af773549c53a6830c510685ef44548c8ea01400e0661c37e65";
|
||||||
|
|
||||||
type directive = enum i32 {
|
type directive = enum i32 {
|
||||||
ERROR = 0,
|
ERROR = 0,
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
/*
|
|
||||||
* 696_modtype_leaf_collision — same-leaf-name cross-module type
|
|
||||||
* disambiguation. Two modules each export `type stream` with
|
|
||||||
* intentionally distinct field sets; the consumer pins both via
|
|
||||||
* `mod1.stream` / `mod2.stream`.
|
|
||||||
*
|
|
||||||
* Pre-fix (before sym.mod tagging + scope_lookup_in_module): the
|
|
||||||
* second `type stream` registration in pass-1 failed with
|
|
||||||
* "duplicate type stream", or when both did survive they collapsed
|
|
||||||
* to whichever sym was first in the bucket chain. The negative case
|
|
||||||
* (`b: mod1.stream; return b.c;`) used to silently bind through the
|
|
||||||
* wrong type and the bogus field read would either crash at run
|
|
||||||
* time or return the wrong byte. Post-fix it must surface as a
|
|
||||||
* compile-time "no field 'c' in stream" error.
|
|
||||||
*
|
|
||||||
* Fixtures live in test/wcc/data/modcollision/. mod1/mod1.ww and
|
|
||||||
* mod2/mod2.ww are deliberately *new* source files so the test
|
|
||||||
* stays self-contained — independent of lib/bufio (now that
|
|
||||||
* `bufio.stream` and `io.stream` coexist on the post-#15 surface).
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_pos(const char *driver, const char *fixdir, const char *tag)
|
|
||||||
{
|
|
||||||
char td[1024], bin[1024], sepwork[1100];
|
|
||||||
snprintf(td, sizeof td, "/tmp/modcol_pos_%d_%s", getpid(), tag);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: mkdir %s failed\n", tag, td);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(bin, sizeof bin, "%s/pos", td);
|
|
||||||
snprintf(sepwork, sizeof sepwork, "%s/pos.sepwork", td);
|
|
||||||
|
|
||||||
char cmd[2048];
|
|
||||||
/* cd into the fixture dir so the driver's source-dir-first import
|
|
||||||
* search resolves `use mod1; use mod2;`; -o puts the binary and the
|
|
||||||
* caller-owned .sepwork under the directory acquired above. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -o %s/pos pos.ww >/dev/null 2>&1",
|
|
||||||
fixdir, driver, td);
|
|
||||||
int fail = 0;
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: pos.ww build failed\n", tag);
|
|
||||||
fail = 1;
|
|
||||||
} else {
|
|
||||||
int got = runwait(bin);
|
|
||||||
/* 3 + 5 (mod1.stream) + 7 + 11 (mod2.stream) = 26. */
|
|
||||||
if (got != 26) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"modtype_leaf[%s]: pos.ww exit=%d want=26\n", tag, got);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", sepwork);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n",
|
|
||||||
tag, sepwork);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (unlink(bin) != 0 && errno != ENOENT) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n", tag, bin);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (rmdir(td) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n", tag, td);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
return fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_neg(const char *driver, const char *fixdir, const char *tag)
|
|
||||||
{
|
|
||||||
char td[1024], bin[1024], sepwork[1100];
|
|
||||||
snprintf(td, sizeof td, "/tmp/modcol_neg_%d_%s", getpid(), tag);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: mkdir %s failed\n", tag, td);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(bin, sizeof bin, "%s/neg", td);
|
|
||||||
snprintf(sepwork, sizeof sepwork, "%s/neg.sepwork", td);
|
|
||||||
|
|
||||||
char cmd[2048];
|
|
||||||
/* Build must fail with a field-resolution error: the `c` field
|
|
||||||
* lives on mod2.stream, not mod1.stream. If pre-fix sym-binding
|
|
||||||
* smashed mod1.stream's identity into mod2.stream's slot, this
|
|
||||||
* would silently succeed. -o puts the binary and its caller-owned
|
|
||||||
* .sepwork (created even on the expected build failure) under the
|
|
||||||
* directory acquired above. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -o %s/neg neg.ww >/dev/null 2>&1",
|
|
||||||
fixdir, driver, td);
|
|
||||||
int rc = runwait(cmd);
|
|
||||||
int fail = 0;
|
|
||||||
if (rc == 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"modtype_leaf[%s]: neg.ww built but should have errored\n",
|
|
||||||
tag);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", sepwork);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n",
|
|
||||||
tag, sepwork);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (unlink(bin) != 0 && errno != ENOENT) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n", tag, bin);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (rmdir(td) != 0) {
|
|
||||||
fprintf(stderr, "modtype_leaf[%s]: cleanup %s failed\n", tag, td);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
return fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
||||||
char wdrv[1024];
|
|
||||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
|
||||||
|
|
||||||
char fixdir[1024];
|
|
||||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
|
||||||
size_t cwd_n = strlen(fixdir);
|
|
||||||
const char *rel = "/test/wcc/data/modcollision";
|
|
||||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
|
||||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
fail += run_pos(cdrv, fixdir, "cstage");
|
|
||||||
fail += run_neg(cdrv, fixdir, "cstage");
|
|
||||||
fail += run_pos(wdrv, fixdir, "wwstage");
|
|
||||||
/* No wwstage neg case: w6c_ww has no checkfile pass, so a
|
|
||||||
* missing-field reference doesn't surface as a build error.
|
|
||||||
* The positive case alone pins the structlookup mod-filter
|
|
||||||
* (without it, `mod2.stream` would either fail to resolve or
|
|
||||||
* silently bind to mod1.stream and miscompute the exit). */
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"modtype_leaf_collision: %d case(s) failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("modtype_leaf_collision: 3/3 ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* 697_samemod_prefer — same-module preference for bare-leaf lookup.
|
|
||||||
*
|
|
||||||
* Two modules each export `read` with intentionally distinct
|
|
||||||
* signatures (mod1: i32 → i32, mod2: str → i32). Each module's
|
|
||||||
* `caller()` invokes bare `read(...)` with its own arg type. The
|
|
||||||
* bare-leaf lookup must resolve to the SAME-MODULE `read`; otherwise
|
|
||||||
* the call is a check-time signature mismatch.
|
|
||||||
*
|
|
||||||
* Pre-fix (flat scope, first-wins by hash order): one of the bare
|
|
||||||
* lookups picked the wrong module's `read` and the build failed with
|
|
||||||
* "argument type not assignable". Post-fix (scope_lookup_prefer): each
|
|
||||||
* bare-leaf inside its own module binds to the same-module entry, both
|
|
||||||
* calls type-check, and the build succeeds.
|
|
||||||
*
|
|
||||||
* Runtime exit codes aren't asserted here because cgen still emits
|
|
||||||
* unmangled `TEXT read` labels for both modules and the linker
|
|
||||||
* collapses them. Mangling fn labels by module is a separate codegen
|
|
||||||
* sweep; this test pins the resolver fix alone.
|
|
||||||
*
|
|
||||||
* Fixtures live in test/wcc/data/samemodprefer/. Same shape as
|
|
||||||
* 696_modtype_leaf_collision.c.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_pos(const char *driver, const char *fixdir, const char *tag)
|
|
||||||
{
|
|
||||||
/* -o puts the binary and caller-owned `<stem>.sepwork/` under the
|
|
||||||
* acquired directory; the cd keeps source-dir-first import search
|
|
||||||
* resolving `use mod1; use mod2;`. */
|
|
||||||
char td[1024], outbin[1024], sepwork[1100];
|
|
||||||
snprintf(td, sizeof td, "/tmp/smprefer_%d_%s", getpid(), tag);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
fprintf(stderr, "samemod_prefer[%s]: mkdir %s failed\n", tag, td);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
snprintf(outbin, sizeof outbin, "%s/pos", td);
|
|
||||||
snprintf(sepwork, sizeof sepwork, "%s/pos.sepwork", td);
|
|
||||||
|
|
||||||
char cmd[2048];
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -o %s/pos pos.ww >/dev/null 2>&1",
|
|
||||||
fixdir, driver, td);
|
|
||||||
int fail = 0;
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"samemod_prefer[%s]: pos.ww build failed — bare-leaf "
|
|
||||||
"`read` lookup likely cross-bound\n", tag);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
snprintf(cmd, sizeof cmd, "rm -rf %s", sepwork);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr, "samemod_prefer[%s]: cleanup %s failed\n",
|
|
||||||
tag, sepwork);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (unlink(outbin) != 0 && errno != ENOENT) {
|
|
||||||
fprintf(stderr, "samemod_prefer[%s]: cleanup %s failed\n",
|
|
||||||
tag, outbin);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
if (rmdir(td) != 0) {
|
|
||||||
fprintf(stderr, "samemod_prefer[%s]: cleanup %s failed\n", tag, td);
|
|
||||||
fail = 1;
|
|
||||||
}
|
|
||||||
return fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
||||||
|
|
||||||
char fixdir[1024];
|
|
||||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
|
||||||
size_t cwd_n = strlen(fixdir);
|
|
||||||
const char *rel = "/test/wcc/data/samemodprefer";
|
|
||||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
|
||||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
fail += run_pos(cdrv, fixdir, "cstage");
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"samemod_prefer: %d case(s) failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("samemod_prefer: 1/1 ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
/*
|
|
||||||
* 699_use_promote_alias — SK_USE→SK_X promotion preserves use_alias.
|
|
||||||
*
|
|
||||||
* Sweep of every reachable promotion site in cmd/wcc/check.c where an
|
|
||||||
* SK_USE leaf gets in-place-promoted to a same-named decl. With driver
|
|
||||||
* concatenation, the imported module's top-level decls appear in
|
|
||||||
* file.list BEFORE the primary's `use foo;`, so pass-1 (N_USE +
|
|
||||||
* N_TYPEDECL only) installs an SK_USE("foo") that pass-1.5 later
|
|
||||||
* promotes when it walks the imported N_DEF / N_FNDECL / N_LET of the
|
|
||||||
* same name. Each promotion must set `prev->use_alias = 1` so the
|
|
||||||
* dot-prefixed lookups in resolve_typename (L77) and N_DOT (L709)
|
|
||||||
* continue to walk the leaf as a module head.
|
|
||||||
*
|
|
||||||
* The fnmatch.flag-style "unknown type" failure that surfaced this is
|
|
||||||
* the SK_FN row; the SK_TYPE row pins the random.random precedent
|
|
||||||
* against regression; SK_DEF / SK_VAR are the same structural shape
|
|
||||||
* with `def` / top-level `let` standing in for `fn`.
|
|
||||||
*
|
|
||||||
* row | imported decl shape | path | check.c line
|
|
||||||
* -----+-----------------------------+----------+-------------
|
|
||||||
* type | `type typmod = struct {...}`| L1660/77 | regression pin
|
|
||||||
* fn | `fn fnmod() i32 = ...` | L1722 | the bug
|
|
||||||
* def | `def defmod: i32 = 0` | L1709 | same shape
|
|
||||||
* var | `let varmod: i32 = 0` | L1736 | same shape
|
|
||||||
*
|
|
||||||
* Fixtures: test/wcc/data/usepromote/{type,fn,def,var}mod/<name>.ww
|
|
||||||
* primary: test/wcc/data/usepromote/pos_{type,fn,def,var}.ww
|
|
||||||
*
|
|
||||||
* Each primary returns `flag.A` (= 42) so a successful build + run +
|
|
||||||
* exit=42 proves the lookup chain end-to-end. Cstage-only: wwstage
|
|
||||||
* uses a coexistence-not-promotion model in selfhost/cmd/wcc/check.ww
|
|
||||||
* (separate sym entries differentiated by `mod`, no use_alias flag at
|
|
||||||
* all), so the bug is structurally non-reachable on that side — see
|
|
||||||
* the architectural note at installdecl in check.ww. Task #11 will
|
|
||||||
* revisit when wwstage grows a real check pass on the cgen path.
|
|
||||||
*/
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
run_row(const char *driver, const char *fixdir, const char *tag)
|
|
||||||
{
|
|
||||||
/* Keep the output and its caller-owned `<stem>.sepwork/` in one
|
|
||||||
* invocation-owned directory outside the repository. */
|
|
||||||
char td[64];
|
|
||||||
snprintf(td, sizeof td, "/tmp/usepr_%d_%s", getpid(), tag);
|
|
||||||
if (mkdir(td, 0755) != 0) {
|
|
||||||
fprintf(stderr, "use_promote_alias[%s]: mkdir %s: %s\n",
|
|
||||||
tag, td, strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
char src[64];
|
|
||||||
snprintf(src, sizeof src, "pos_%s.ww", tag);
|
|
||||||
char bin[2048], sepdir[2048], rmcmd[2112];
|
|
||||||
snprintf(bin, sizeof bin, "%s/out", td);
|
|
||||||
snprintf(sepdir, sizeof sepdir, "%s.sepwork", bin);
|
|
||||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir);
|
|
||||||
char cmd[2048];
|
|
||||||
int failed = 0, got;
|
|
||||||
/* cd into the fixture dir so the driver's source-dir-first
|
|
||||||
* import search resolves `use <tag>mod;`; -o points OUT of the repo. */
|
|
||||||
snprintf(cmd, sizeof cmd,
|
|
||||||
"cd %s && %s build -o %s %s >/dev/null 2>&1", fixdir, driver, bin, src);
|
|
||||||
if (runwait(cmd) != 0) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"use_promote_alias[%s]: build failed — SK_USE→SK_X "
|
|
||||||
"promotion likely dropped use_alias\n", tag);
|
|
||||||
failed = 1;
|
|
||||||
goto cleanup;
|
|
||||||
}
|
|
||||||
got = runwait(bin);
|
|
||||||
if (got != 42) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"use_promote_alias[%s]: exit=%d want=42\n", tag, got);
|
|
||||||
failed = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
{
|
|
||||||
int cleanup_failed = 0;
|
|
||||||
if (runwait(rmcmd) != 0) {
|
|
||||||
fprintf(stderr, "use_promote_alias[%s]: remove %s failed\n",
|
|
||||||
tag, sepdir);
|
|
||||||
cleanup_failed = 1;
|
|
||||||
}
|
|
||||||
if (unlink(bin) != 0 && errno != ENOENT) {
|
|
||||||
fprintf(stderr, "use_promote_alias[%s]: unlink %s: %s\n",
|
|
||||||
tag, bin, strerror(errno));
|
|
||||||
cleanup_failed = 1;
|
|
||||||
}
|
|
||||||
if (rmdir(td) != 0) {
|
|
||||||
fprintf(stderr, "use_promote_alias[%s]: rmdir %s: %s\n",
|
|
||||||
tag, td, strerror(errno));
|
|
||||||
cleanup_failed = 1;
|
|
||||||
}
|
|
||||||
if (!failed && cleanup_failed) failed = 1;
|
|
||||||
}
|
|
||||||
return failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
|
||||||
|
|
||||||
char fixdir[1024];
|
|
||||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
|
||||||
size_t cwd_n = strlen(fixdir);
|
|
||||||
const char *rel = "/test/wcc/data/usepromote";
|
|
||||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
|
||||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
|
||||||
|
|
||||||
int fail = 0;
|
|
||||||
fail += run_row(cdrv, fixdir, "type");
|
|
||||||
fail += run_row(cdrv, fixdir, "fn");
|
|
||||||
fail += run_row(cdrv, fixdir, "def");
|
|
||||||
fail += run_row(cdrv, fixdir, "var");
|
|
||||||
|
|
||||||
if (fail) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"use_promote_alias: %d row(s) failed\n", fail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("use_promote_alias: 4/4 ok\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// modcollision/mod1 — exports `type stream` with mod1-shaped fields.
|
|
||||||
// Paired with mod2/mod2.ww to exercise same-leaf-name cross-module
|
|
||||||
// type disambiguation. Test driver: 696_modtype_leaf_collision.c.
|
|
||||||
|
|
||||||
package mod1;
|
|
||||||
|
|
||||||
export type stream = struct {
|
|
||||||
a: i32,
|
|
||||||
b: i32,
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// modcollision/mod2 — exports `type stream` with mod2-shaped fields.
|
|
||||||
// Paired with mod1/mod1.ww to exercise same-leaf-name cross-module
|
|
||||||
// type disambiguation. Test driver: 696_modtype_leaf_collision.c.
|
|
||||||
//
|
|
||||||
// Fields are intentionally named distinctly from mod1's (c/d vs a/b)
|
|
||||||
// so the negative test (`b: mod1.stream` accessed via mod2-only field
|
|
||||||
// 'c') surfaces as a compile-time field-resolution error.
|
|
||||||
|
|
||||||
package mod2;
|
|
||||||
|
|
||||||
export type stream = struct {
|
|
||||||
c: i32,
|
|
||||||
d: i32,
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// Negative case: declare `b: mod1.stream` then access a mod2-only
|
|
||||||
// field. With the mod-tagged scope lookup `b` resolves to mod1.stream
|
|
||||||
// (fields a, b), so the field access `b.c` must be a compile-time
|
|
||||||
// error rather than silently binding to mod2.stream and succeeding.
|
|
||||||
//
|
|
||||||
// No cross-module call is made: a missing mod1.make would itself
|
|
||||||
// trigger a link-time error that could mask the field-resolution
|
|
||||||
// error this test is actually pinning. `let b: mod1.stream;` is
|
|
||||||
// enough — we read b.c before initializing it, which is fine because
|
|
||||||
// the field-resolution error fires at check, long before any reach
|
|
||||||
// analysis or codegen runs.
|
|
||||||
|
|
||||||
package modcollision;
|
|
||||||
|
|
||||||
import mod1;
|
|
||||||
import mod2;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let b: mod1.stream;
|
|
||||||
return b.c; // mod1.stream has no `c`; expect a compile error.
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Positive case: two modules each export `type stream`. The consumer
|
|
||||||
// imports both and disambiguates via the module qualifier. mod1.stream
|
|
||||||
// has fields (a, b); mod2.stream has fields (c, d). The exit code
|
|
||||||
// encodes a sum of all four fields, so any cross-binding would either
|
|
||||||
// fail to compile or return the wrong value.
|
|
||||||
|
|
||||||
package modcollision;
|
|
||||||
|
|
||||||
import mod1;
|
|
||||||
import mod2;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let s1: mod1.stream;
|
|
||||||
s1.a = 3: i32;
|
|
||||||
s1.b = 5: i32;
|
|
||||||
let s2: mod2.stream;
|
|
||||||
s2.c = 7: i32;
|
|
||||||
s2.d = 11: i32;
|
|
||||||
return s1.a + s1.b + s2.c + s2.d;
|
|
||||||
};
|
|
||||||
19
test/wcc/data/r696_modtype_leaf_neg/case.ww
Normal file
19
test/wcc/data/r696_modtype_leaf_neg/case.ww
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//ww:error c "no field 'c' in stream" ww "asserttyped: dot"
|
||||||
|
// migrated from test/wcc/696_modtype_leaf_collision.c: field `c` lives only on mod2.stream; pre-fix the read silently bound through the wrong type.
|
||||||
|
package mod1;
|
||||||
|
export type stream = struct {
|
||||||
|
a: i32,
|
||||||
|
b: i32,
|
||||||
|
};
|
||||||
|
package mod2;
|
||||||
|
export type stream = struct {
|
||||||
|
c: i32,
|
||||||
|
d: i32,
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import mod1;
|
||||||
|
import mod2;
|
||||||
|
fn main() i32 = {
|
||||||
|
let b: mod1.stream;
|
||||||
|
return b.c;
|
||||||
|
};
|
||||||
24
test/wcc/data/r696_modtype_leaf_pos/case.ww
Normal file
24
test/wcc/data/r696_modtype_leaf_pos/case.ww
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
//ww:run-exit 26
|
||||||
|
// migrated from test/wcc/696_modtype_leaf_collision.c: same-leaf cross-module `type stream` must stay mod-tagged (pre-fix sym smash cross-bound the fields).
|
||||||
|
package mod1;
|
||||||
|
export type stream = struct {
|
||||||
|
a: i32,
|
||||||
|
b: i32,
|
||||||
|
};
|
||||||
|
package mod2;
|
||||||
|
export type stream = struct {
|
||||||
|
c: i32,
|
||||||
|
d: i32,
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import mod1;
|
||||||
|
import mod2;
|
||||||
|
fn main() i32 = {
|
||||||
|
let s1: mod1.stream;
|
||||||
|
s1.a = 3: i32;
|
||||||
|
s1.b = 5: i32;
|
||||||
|
let s2: mod2.stream;
|
||||||
|
s2.c = 7: i32;
|
||||||
|
s2.d = 11: i32;
|
||||||
|
return s1.a + s1.b + s2.c + s2.d;
|
||||||
|
};
|
||||||
22
test/wcc/data/r697_samemod_prefer/case.ww
Normal file
22
test/wcc/data/r697_samemod_prefer/case.ww
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
//ww:compile
|
||||||
|
// migrated from test/wcc/697_samemod_prefer.c: bare-leaf `read` binds same-module first; compile-only (unmangled TEXT read labels still collapse in the linker).
|
||||||
|
package mod1;
|
||||||
|
export fn read(x: i32) i32 = {
|
||||||
|
return x + 100i32;
|
||||||
|
};
|
||||||
|
export fn caller() i32 = {
|
||||||
|
return read(1i32);
|
||||||
|
};
|
||||||
|
package mod2;
|
||||||
|
export fn read(x: str) i32 = {
|
||||||
|
return x.len + 200i32;
|
||||||
|
};
|
||||||
|
export fn caller() i32 = {
|
||||||
|
return read("ok");
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import mod1;
|
||||||
|
import mod2;
|
||||||
|
fn main() i32 = {
|
||||||
|
return 0i32;
|
||||||
|
};
|
||||||
14
test/wcc/data/r699_usepromote_def/case.ww
Normal file
14
test/wcc/data/r699_usepromote_def/case.ww
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//ww:run-exit 42
|
||||||
|
// migrated from test/wcc/699_use_promote_alias.c: SK_USE->SK_DEF promotion must keep use_alias so `defmod.flag` resolves.
|
||||||
|
package defmod;
|
||||||
|
export def defmod: i32 = 0i32;
|
||||||
|
export type flag = enum i32 {
|
||||||
|
NONE = 0,
|
||||||
|
A = 42,
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import defmod;
|
||||||
|
fn main() i32 = {
|
||||||
|
let m: defmod.flag = defmod.flag.A;
|
||||||
|
return m: i32;
|
||||||
|
};
|
||||||
16
test/wcc/data/r699_usepromote_fn/case.ww
Normal file
16
test/wcc/data/r699_usepromote_fn/case.ww
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//ww:run-exit 42
|
||||||
|
// migrated from test/wcc/699_use_promote_alias.c: SK_USE->SK_FN promotion (the original bug) must keep use_alias or `fnmod.flag` fails "unknown type".
|
||||||
|
package fnmod;
|
||||||
|
export type flag = enum i32 {
|
||||||
|
NONE = 0,
|
||||||
|
A = 42,
|
||||||
|
};
|
||||||
|
export fn fnmod() i32 = {
|
||||||
|
return 0i32;
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import fnmod;
|
||||||
|
fn main() i32 = {
|
||||||
|
let m: fnmod.flag = fnmod.flag.A;
|
||||||
|
return m: i32;
|
||||||
|
};
|
||||||
16
test/wcc/data/r699_usepromote_type/case.ww
Normal file
16
test/wcc/data/r699_usepromote_type/case.ww
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//ww:run-exit 42
|
||||||
|
// migrated from test/wcc/699_use_promote_alias.c: SK_USE->SK_TYPE in-place promotion keeps use_alias so `typmod.flag` resolves (check.c self-import branch).
|
||||||
|
package typmod;
|
||||||
|
export type typmod = struct {
|
||||||
|
x: i32,
|
||||||
|
};
|
||||||
|
export type flag = enum i32 {
|
||||||
|
NONE = 0,
|
||||||
|
A = 42,
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import typmod;
|
||||||
|
fn main() i32 = {
|
||||||
|
let m: typmod.flag = typmod.flag.A;
|
||||||
|
return m: i32;
|
||||||
|
};
|
||||||
14
test/wcc/data/r699_usepromote_var/case.ww
Normal file
14
test/wcc/data/r699_usepromote_var/case.ww
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//ww:run-exit 42
|
||||||
|
// migrated from test/wcc/699_use_promote_alias.c: SK_USE->SK_VAR promotion must keep use_alias so `varmod.flag` resolves.
|
||||||
|
package varmod;
|
||||||
|
export let varmod: i32 = 0i32;
|
||||||
|
export type flag = enum i32 {
|
||||||
|
NONE = 0,
|
||||||
|
A = 42,
|
||||||
|
};
|
||||||
|
package main;
|
||||||
|
import varmod;
|
||||||
|
fn main() i32 = {
|
||||||
|
let m: varmod.flag = varmod.flag.A;
|
||||||
|
return m: i32;
|
||||||
|
};
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// samemodprefer/mod1 — exports a `read` that takes an i32 plus a
|
|
||||||
// `caller` that invokes bare `read(1i32)`. The bare-leaf lookup must
|
|
||||||
// resolve to mod1.read (i32 → i32) rather than mod2.read (str → i32),
|
|
||||||
// which coexists in the flat scope. Pre-fix the lookup picked
|
|
||||||
// whichever entry hashed earliest into the bucket; a wrong bind would
|
|
||||||
// surface as a check-time signature mismatch. Paired with mod2/mod2.ww
|
|
||||||
// and test/wcc/697_samemod_prefer.c.
|
|
||||||
|
|
||||||
package mod1;
|
|
||||||
|
|
||||||
export fn read(x: i32) i32 = {
|
|
||||||
return x + 100i32;
|
|
||||||
};
|
|
||||||
|
|
||||||
export fn caller() i32 = {
|
|
||||||
// Bare-leaf call inside mod1: must bind to mod1.read (i32 arg).
|
|
||||||
// If preference doesn't kick in this fails check: mod2.read
|
|
||||||
// expects a str, so `read(1i32)` would be a signature mismatch.
|
|
||||||
return read(1i32);
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// samemodprefer/mod2 — exports a `read` that takes a str plus a
|
|
||||||
// `caller` that invokes bare `read("ok")`. The bare-leaf lookup must
|
|
||||||
// resolve to mod2.read (str → i32) rather than mod1.read (i32 → i32),
|
|
||||||
// which coexists in the flat scope. Pre-fix the lookup picked
|
|
||||||
// whichever entry hashed earliest into the bucket; a wrong bind would
|
|
||||||
// surface as a check-time signature mismatch. Paired with mod1/mod1.ww
|
|
||||||
// and test/wcc/697_samemod_prefer.c.
|
|
||||||
|
|
||||||
package mod2;
|
|
||||||
|
|
||||||
export fn read(x: str) i32 = {
|
|
||||||
return x.len + 200i32;
|
|
||||||
};
|
|
||||||
|
|
||||||
export fn caller() i32 = {
|
|
||||||
// Bare-leaf call inside mod2: must bind to mod2.read (str arg).
|
|
||||||
// If preference doesn't kick in this fails check: mod1.read
|
|
||||||
// expects an i32, so `read("ok")` would be a signature mismatch.
|
|
||||||
return read("ok");
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Positive case: two modules each export `read` with DIFFERENT
|
|
||||||
// signatures (mod1: i32 → i32, mod2: str → i32). Each module also
|
|
||||||
// exports a `caller` that invokes bare `read(...)` with its own arg
|
|
||||||
// type. The bare-leaf inside mod1 must bind to mod1.read; inside mod2
|
|
||||||
// to mod2.read. If preference doesn't kick in and the bare lookup
|
|
||||||
// picks the other module's `read`, the build fails with a signature
|
|
||||||
// mismatch at check-time. The driver test asserts that this fixture
|
|
||||||
// builds successfully.
|
|
||||||
//
|
|
||||||
// We don't assert runtime exit codes here because cgen still emits
|
|
||||||
// unmangled `TEXT read` labels for both modules and the linker
|
|
||||||
// collapses them — that's a separate codegen sweep, orthogonal to the
|
|
||||||
// resolver fix this test pins.
|
|
||||||
|
|
||||||
package samemodprefer;
|
|
||||||
|
|
||||||
import mod1;
|
|
||||||
import mod2;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
return 0i32;
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// usepromote/defmod — exports a `def defmod` with the same leaf as
|
|
||||||
// the module name. Paired with pos_def.ww to exercise the SK_USE→
|
|
||||||
// SK_DEF promotion (cmd/wcc/check.c L1709). Mirrors the SK_FN bug
|
|
||||||
// shape: without `use_alias = 1`, the consumer's `defmod.flag` lookup
|
|
||||||
// fails because the promoted-in-place SK_DEF leaf no longer advertises
|
|
||||||
// itself as a module head.
|
|
||||||
|
|
||||||
package defmod;
|
|
||||||
|
|
||||||
export def defmod: i32 = 0i32;
|
|
||||||
|
|
||||||
export type flag = enum i32 {
|
|
||||||
NONE = 0,
|
|
||||||
A = 42,
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// usepromote/fnmod — exports a `fn fnmod()` with the same leaf as the
|
|
||||||
// module name. Paired with pos_fn.ww to exercise the bug fix at
|
|
||||||
// cmd/wcc/check.c L1722 (SK_USE→SK_FN promotion was missing the
|
|
||||||
// `use_alias = 1` line, so the consumer's `fnmod.flag` lookup walked
|
|
||||||
// resolve_typename's dot branch, found head `fnmod` as SK_FN, and
|
|
||||||
// skipped the module-filtered leaf path → "unknown type fnmod.flag".
|
|
||||||
//
|
|
||||||
// lib/fnmatch is the real-world instance that surfaced this.
|
|
||||||
|
|
||||||
package fnmod;
|
|
||||||
|
|
||||||
export type flag = enum i32 {
|
|
||||||
NONE = 0,
|
|
||||||
A = 42,
|
|
||||||
};
|
|
||||||
|
|
||||||
export fn fnmod() i32 = {
|
|
||||||
return 0i32;
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// SK_USE→SK_DEF promotion. Same shape as pos_fn.ww but the imported
|
|
||||||
// module exports `def defmod: i32 = 0` instead of `fn defmod()`.
|
|
||||||
// Pass-1.5 N_DEF case (check.c L1709) promotes the SK_USE leaf to
|
|
||||||
// SK_DEF. Pre-fix it forgot use_alias=1, so `defmod.flag` resolution
|
|
||||||
// failed. Post-fix the build succeeds and exit code = flag.A = 42.
|
|
||||||
|
|
||||||
package usepromote;
|
|
||||||
|
|
||||||
import defmod;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let m: defmod.flag = defmod.flag.A;
|
|
||||||
return m: i32;
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// SK_USE→SK_FN promotion (the bug). fnmod exports `fn fnmod()` and
|
|
||||||
// `type flag = enum`. Pass-1 installs SK_TYPE("flag", mod="fnmod") and
|
|
||||||
// SK_USE("fnmod") (the `fn fnmod` isn't installed in pass-1; pass-1
|
|
||||||
// only handles N_USE + N_TYPEDECL). Pass-1.5 then sees the N_FNDECL
|
|
||||||
// "fnmod", finds the SK_USE local, and promotes in place to SK_FN.
|
|
||||||
// Pre-fix that promotion forgot `use_alias = 1`, so `fnmod.flag`
|
|
||||||
// resolution failed with "unknown type fnmod.flag". Post-fix the
|
|
||||||
// build succeeds and exit code = flag.A = 42.
|
|
||||||
|
|
||||||
package usepromote;
|
|
||||||
|
|
||||||
import fnmod;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let m: fnmod.flag = fnmod.flag.A;
|
|
||||||
return m: i32;
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// SK_USE→SK_TYPE regression pin. typmod exports `type typmod = ...`
|
|
||||||
// AND `type flag = enum`. Driver concatenation puts imported decls
|
|
||||||
// first, so pass-1 installs SK_TYPE("typmod") first; the primary's
|
|
||||||
// `use typmod` then hits the self-import branch (check.c L1660) and
|
|
||||||
// sets use_alias=1 on the SK_TYPE entry. We rely on use_alias=1 here
|
|
||||||
// so the dot-prefixed `typmod.flag` lookup resolves to mod="typmod"'s
|
|
||||||
// flag entry. Exit code = flag.A = 42 verifies end-to-end.
|
|
||||||
|
|
||||||
package usepromote;
|
|
||||||
|
|
||||||
import typmod;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let m: typmod.flag = typmod.flag.A;
|
|
||||||
return m: i32;
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// SK_USE→SK_VAR promotion. Imported module exports a top-level
|
|
||||||
// `let varmod: i32 = 0`. Pass-1.5 N_LET case (check.c L1736) promotes
|
|
||||||
// the SK_USE leaf to SK_VAR. Pre-fix it forgot use_alias=1, so
|
|
||||||
// `varmod.flag` resolution failed. Post-fix the build succeeds and
|
|
||||||
// exit code = flag.A = 42.
|
|
||||||
|
|
||||||
package usepromote;
|
|
||||||
|
|
||||||
import varmod;
|
|
||||||
|
|
||||||
fn main() i32 = {
|
|
||||||
let m: varmod.flag = varmod.flag.A;
|
|
||||||
return m: i32;
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// usepromote/typmod — exports a `type typmod` with the same leaf as
|
|
||||||
// the module name. Paired with pos_type.ww to pin the SK_USE→SK_TYPE
|
|
||||||
// promotion's use_alias=1 behaviour (cmd/wcc/check.c L1660 / L1677):
|
|
||||||
// the consumer's `typmod.flag` lookup must walk through the alias
|
|
||||||
// even though the leaf sym is SK_TYPE, not SK_USE.
|
|
||||||
//
|
|
||||||
// random.random in lib/ is the canonical real-world instance of this
|
|
||||||
// shape; this fixture replays it as a regression pin.
|
|
||||||
|
|
||||||
package typmod;
|
|
||||||
|
|
||||||
export type typmod = struct {
|
|
||||||
x: i32,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type flag = enum i32 {
|
|
||||||
NONE = 0,
|
|
||||||
A = 42,
|
|
||||||
};
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// usepromote/varmod — exports a top-level `let varmod` with the same
|
|
||||||
// leaf as the module name. Paired with pos_var.ww to exercise the
|
|
||||||
// SK_USE→SK_VAR promotion at cmd/wcc/check.c L1736. Same shape as
|
|
||||||
// SK_DEF / SK_FN — without `use_alias = 1`, the consumer's
|
|
||||||
// `varmod.flag` lookup fails.
|
|
||||||
|
|
||||||
package varmod;
|
|
||||||
|
|
||||||
export let varmod: i32 = 0i32;
|
|
||||||
|
|
||||||
export type flag = enum i32 {
|
|
||||||
NONE = 0,
|
|
||||||
A = 42,
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user