wcc/check: reject self-import, both stages (#16 ENFORCE-checker)
check-(c): a package importing itself (any spelling) is a hard error, mirroring Go. Predicate is leaf==owner at the N_USE/installdecl seam — sound only after the PREP commits (dotted-test renames, package-less boundary directive). Identical wording both stages; diagnostics-only, byte-id-neutral. 948 pins the reject in both compilers; 708's pos_selfimp (which pinned the abolished self-import skip) converts to neg_selfimp + new pos_crossmod preserving the param-shadow tolerance the case existed for. Checks (a) unused and (b)/(d) name-membership stay deferred to the multi-package arc: imports are filename-keyed pulls, so those need import->file provenance this compiler lacks.
This commit is contained in:
9
Makefile
9
Makefile
@@ -234,7 +234,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_w6l $(BIN)/test_data_link \
|
||||
$(BIN)/test_arch \
|
||||
$(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \
|
||||
$(BIN)/test_at_test $(BIN)/test_let_global $(BIN)/test_def_neg_global \
|
||||
$(BIN)/test_at_test $(BIN)/test_selfimport \
|
||||
$(BIN)/test_let_global $(BIN)/test_def_neg_global \
|
||||
$(BIN)/test_def_const_fold \
|
||||
$(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \
|
||||
$(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \
|
||||
@@ -594,6 +595,10 @@ $(BIN)/test_at_test: test/wcc/910_at_test.c $(BIN)/ww $(BIN)/w6c $(BIN)/w6a \
|
||||
$(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_selfimport: test/wcc/948_selfimport.c $(BIN)/w6c $(BIN)/w6c_ww \
|
||||
| $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
@@ -928,7 +933,7 @@ $(BIN)/test_cgreturn_variant_zero: test/wcc/707_cgreturn_variant_zero.c \
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_param_shadow_mod: test/wcc/708_param_shadow_mod.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
|
||||
@@ -2810,6 +2810,16 @@ check_file(Checker *c, Node *file)
|
||||
* walked in the next pass. */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind == N_USE) {
|
||||
/* check-(c) self-import: a package may not import
|
||||
* itself. Pure owner==leaf string compare, package-
|
||||
* model-independent — sound under ww's filename-keyed
|
||||
* file-inclusion imports. check-(a) unused and
|
||||
* (b)/(d) membership DEFERRED to task #8 (filename-
|
||||
* keyed pulls lack import->file->symbol provenance). */
|
||||
const char *owner = decl_mod(file, d);
|
||||
if (owner && owner[0] && strcmp(d->str, owner) == 0)
|
||||
err(c, d->pos, "self-import: package "
|
||||
"'%s' cannot import itself", owner);
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
if (prev != NULL) {
|
||||
/* Self-import: the driver concatenates the
|
||||
|
||||
@@ -10607,7 +10607,18 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
// check-(c) self-import: a package may not import itself. Pure
|
||||
// owner==leaf string compare, package-model-independent. check-(a)
|
||||
// unused + (b)/(d) membership DEFERRED to task #8 (filename-keyed
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
|
||||
@@ -275,7 +275,18 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
// check-(c) self-import: a package may not import itself. Pure
|
||||
// owner==leaf string compare, package-model-independent. check-(a)
|
||||
// unused + (b)/(d) membership DEFERRED to task #8 (filename-keyed
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
|
||||
@@ -10607,7 +10607,18 @@ fn installdecl(c: *checker, file: *node, d: *node) void = {
|
||||
let k: nkind = d.kind;
|
||||
let nm: str = d.str;
|
||||
let mod: str = declmod(file, d);
|
||||
if (k == nkind.N_USE) { scopedefine(c.top, nm, skind.SK_USE, nil, d); return; };
|
||||
// check-(c) self-import: a package may not import itself. Pure
|
||||
// owner==leaf string compare, package-model-independent. check-(a)
|
||||
// unused + (b)/(d) membership DEFERRED to task #8 (filename-keyed
|
||||
// pulls lack import->file->symbol provenance). Message byte-identical
|
||||
// to cstage check.c.
|
||||
if (k == nkind.N_USE) {
|
||||
if (mod.len != 0 && streq(nm, mod)) {
|
||||
cerr("self-import: package '"); cerr(mod);
|
||||
cerr("' cannot import itself\n"); c.errs += 1i32;
|
||||
};
|
||||
scopedefine(c.top, nm, skind.SK_USE, nil, d); return;
|
||||
};
|
||||
if (k == nkind.N_DEF) { scopedefineinmodule(c.top, nm, mod, skind.SK_DEF, nil, d); return; };
|
||||
if (k == nkind.N_TYPEDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_TYPE, nil, d); return; };
|
||||
if (k == nkind.N_FNDECL) { scopedefineinmodule(c.top, nm, mod, skind.SK_FN, nil, d); return; };
|
||||
|
||||
@@ -25,13 +25,26 @@
|
||||
* neg_forrange_tuple | `for (let (shadowmod, x) .. s)` | fail
|
||||
* neg_mcase | `match (r) { case let shadowmod ... }` | fail
|
||||
* pos_rename | rename param away from `shadowmod` | exit=42
|
||||
* neg_selfimp | `package selfimp; import selfimp;` | fail (both stages)
|
||||
* pos_crossmod | param named like a CROSS-imported mod | exit=2
|
||||
*
|
||||
* Fixtures live in test/wcc/data/paramshadowmod/. Cstage-only:
|
||||
* wwstage's check.ww runs only inside wwdump_ww (diagnostic), and
|
||||
* the actual selfhost compile pipeline (994_w6c_ww) doesn't trip
|
||||
* because wwstage's cgen takes the module-qualified emit path for
|
||||
* any N_DOT-callee bare ident — see STATUS.md's `Wwstage no-
|
||||
* checkfile-pass smell` note + #11 (deferred wwstage checkfile pass).
|
||||
* Fixtures live in test/wcc/data/paramshadowmod/. The shadow-rule rows
|
||||
* (the neg_ rows + pos_rename) are cstage-only: wwstage's check.ww runs
|
||||
* inside wwdump_ww (diagnostic), and the actual selfhost compile
|
||||
* pipeline (994_w6c_ww) doesn't trip because wwstage's cgen takes the
|
||||
* module-qualified emit path for any N_DOT-callee bare ident — see
|
||||
* STATUS.md's `Wwstage no-checkfile-pass smell` note.
|
||||
*
|
||||
* neg_selfimp / pos_crossmod (#16): pos_selfimp's old positive scenario
|
||||
* (a self-import skipped from the shadow scan so a same-named param
|
||||
* doesn't trip) is ABOLISHED — #16 check-(c) hard-rejects self-imports.
|
||||
* neg_selfimp converts it: `package selfimp; import selfimp;` must be
|
||||
* REJECTED by BOTH w6c and w6c_ww (compile-only, -o /dev/null — rule-14-
|
||||
* safe, no wwstage driver) with the "self-import" diagnostic. pos_crossmod
|
||||
* preserves the surviving shadow-TOLERANCE in legit form: src_imports
|
||||
* filters by the binding's own module, so a param named like a module a
|
||||
* SIBLING module imports (not this one) does NOT trip. The now-unreachable
|
||||
* self-import-skip arm in check_module_shadow is task #13 (not removed here).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -95,33 +108,74 @@ run_pos(const char *driver, const char *fixdir)
|
||||
}
|
||||
|
||||
/*
|
||||
* pos_selfimp — same-module self-import. selfimp/selfimptest.ww
|
||||
* carries `use selfimp;` from inside the module whose tag is also
|
||||
* "selfimp" (matches lib/fmt/fmttest.ww's shape that surfaced the
|
||||
* over-trigger originally). check_module_shadow's u->module ==
|
||||
* u->str skip must drop the directive from the import scan, so the
|
||||
* `selfimp: str` param does NOT trip the rule.
|
||||
* neg_selfimp — the bare self-import `package selfimp; import selfimp;`
|
||||
* (selfimp/selfimptest.ww). #16 check-(c) hard-rejects it; both w6c and
|
||||
* w6c_ww must fail with the "self-import" diagnostic. Compile-only
|
||||
* (-o /dev/null): w6c/w6c_ww do not expand imports, but check-(c) fires
|
||||
* at the N_USE install seam before resolution, so a lone file rejects
|
||||
* directly — NOT a wwstage driver invocation, so rule-14-safe in 7xx.
|
||||
*/
|
||||
static int
|
||||
run_pos_selfimp(const char *driver, const char *fixdir)
|
||||
run_neg_selfimp(const char *comp, const char *fixdir, const char *tag)
|
||||
{
|
||||
char errp[96], cmd[2048];
|
||||
snprintf(errp, sizeof errp, "/tmp/psm_selfimp_%s_%d.err", tag, getpid());
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s selfimp/selfimptest.ww -o /dev/null 2>%s",
|
||||
fixdir, comp, errp);
|
||||
int rc = runwait(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: accepted "
|
||||
"self-import (expected reject)\n", tag);
|
||||
unlink(errp);
|
||||
return 1;
|
||||
}
|
||||
FILE *f = fopen(errp, "rb");
|
||||
int found = 0;
|
||||
if (f) {
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
found = strstr(buf, "self-import") != NULL;
|
||||
}
|
||||
unlink(errp);
|
||||
if (!found) {
|
||||
fprintf(stderr, "param_shadow_mod[neg_selfimp-%s]: rejected "
|
||||
"but no 'self-import' on stderr\n", tag);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* pos_crossmod — surviving shadow-TOLERANCE in legit form. The bundle
|
||||
* imports `shadowmod` from module paramshadowmod and pulls module
|
||||
* `crossmod`, whose probe() has a param named `shadowmod`. src_imports
|
||||
* filters by the binding's own module, so crossmod's param does NOT trip
|
||||
* (crossmod carries no `import shadowmod`) even though a sibling module
|
||||
* imports that leaf. Build + run; exit = 2 (len "hi").
|
||||
*/
|
||||
static int
|
||||
run_pos_crossmod(const char *driver, const char *fixdir)
|
||||
{
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build selfimp/selfimptest.ww >/dev/null 2>&1",
|
||||
"cd %s && %s build pos_crossmod.ww >/dev/null 2>&1",
|
||||
fixdir, driver);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"param_shadow_mod[pos_selfimp]: build failed — "
|
||||
"self-import skip regressed\n");
|
||||
"param_shadow_mod[pos_crossmod]: build failed — shadow "
|
||||
"rule over-triggered on a cross-module param\n");
|
||||
return 1;
|
||||
}
|
||||
char bin[2048];
|
||||
snprintf(bin, sizeof bin, "%s/selfimptest", fixdir);
|
||||
snprintf(bin, sizeof bin, "%s/pos_crossmod", fixdir);
|
||||
int got = runwait(bin);
|
||||
unlink(bin);
|
||||
if (got != 7) {
|
||||
if (got != 2) {
|
||||
fprintf(stderr,
|
||||
"param_shadow_mod[pos_selfimp]: exit=%d want=7\n", got);
|
||||
"param_shadow_mod[pos_crossmod]: exit=%d want=2\n", got);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@@ -140,8 +194,10 @@ main(void)
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[1024];
|
||||
char cdrv[1024], w6c[1024], w6c_ww[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
|
||||
char fixdir[1024];
|
||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
||||
@@ -150,21 +206,27 @@ main(void)
|
||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
||||
|
||||
int fail = 0;
|
||||
fail += run_neg(cdrv, fixdir, "param");
|
||||
fail += run_neg(cdrv, fixdir, "let");
|
||||
fail += run_neg(cdrv, fixdir, "mlet");
|
||||
fail += run_neg(cdrv, fixdir, "forrange_single");
|
||||
fail += run_neg(cdrv, fixdir, "forrange_tuple");
|
||||
fail += run_neg(cdrv, fixdir, "mcase");
|
||||
fail += run_pos(cdrv, fixdir);
|
||||
fail += run_pos_selfimp(cdrv, fixdir);
|
||||
int fail = 0, total = 0;
|
||||
total++; fail += run_neg(cdrv, fixdir, "param");
|
||||
total++; fail += run_neg(cdrv, fixdir, "let");
|
||||
total++; fail += run_neg(cdrv, fixdir, "mlet");
|
||||
total++; fail += run_neg(cdrv, fixdir, "forrange_single");
|
||||
total++; fail += run_neg(cdrv, fixdir, "forrange_tuple");
|
||||
total++; fail += run_neg(cdrv, fixdir, "mcase");
|
||||
total++; fail += run_pos(cdrv, fixdir);
|
||||
/* neg_selfimp: both compilers reject the self-import (compile-only,
|
||||
* rule-14-safe). w6c always; w6c_ww when built. */
|
||||
total++; fail += run_neg_selfimp(w6c, fixdir, "cstage");
|
||||
if (access(w6c_ww, X_OK) == 0) {
|
||||
total++; fail += run_neg_selfimp(w6c_ww, fixdir, "wwstage");
|
||||
}
|
||||
total++; fail += run_pos_crossmod(cdrv, fixdir);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"param_shadow_mod: %d row(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("param_shadow_mod: 8/8 ok\n");
|
||||
printf("param_shadow_mod: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
|
||||
89
test/wcc/948_selfimport.c
Normal file
89
test/wcc/948_selfimport.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 948_selfimport — #16 check-(c): a package may not import itself.
|
||||
*
|
||||
* Both compiler stages (w6c, w6c_ww) must loud-reject `package foo;
|
||||
* import foo;` — nonzero exit AND a "self-import" diagnostic on stderr.
|
||||
* Compile-only (`-o /dev/null`): w6c/w6c_ww do not expand imports (the
|
||||
* driver does), so a lone fixture triggers check-(c) directly. This is
|
||||
* NOT a ww_ww DRIVER test — it never invokes the driver — so it is
|
||||
* Phase-1-safe and does not race the 990-997 byte-id gates (rule 14).
|
||||
*
|
||||
* check-(a) unused + (b)/(d) membership are DEFERRED to task #8 (ww's
|
||||
* filename-keyed file-inclusion imports lack import->file->symbol
|
||||
* provenance); only self-import is package-model-independent.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *
|
||||
absbin(void)
|
||||
{
|
||||
const char *b = getenv("BIN");
|
||||
if (!b) b = "out/bin";
|
||||
if (b[0] == '/') return b;
|
||||
static char buf[2048];
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return NULL;
|
||||
snprintf(buf, sizeof buf, "%s/%s", cwd, b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* reject — `<comp> <fixture> -o /dev/null` must exit nonzero and print
|
||||
* <substr> on stderr. */
|
||||
static int
|
||||
reject(const char *bin, const char *comp, const char *fixture,
|
||||
const char *substr)
|
||||
{
|
||||
int pid = getpid();
|
||||
char errf[256], cmd[4096], line[4096];
|
||||
snprintf(errf, sizeof errf, "/tmp/si948_%s_%d.err", comp, pid);
|
||||
snprintf(cmd, sizeof cmd, "%s/%s %s -o /dev/null 2>%s",
|
||||
bin, comp, fixture, errf);
|
||||
|
||||
int rc = system(cmd);
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "948 FAIL: %s accepted %s (expected reject)\n",
|
||||
comp, fixture);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
FILE *f = fopen(errf, "r");
|
||||
if (f) {
|
||||
while (fgets(line, sizeof line, f))
|
||||
if (strstr(line, substr)) { found = 1; break; }
|
||||
fclose(f);
|
||||
}
|
||||
unlink(errf);
|
||||
if (!found) {
|
||||
fprintf(stderr, "948 FAIL: %s rejected %s but no '%s' on stderr\n",
|
||||
comp, fixture, substr);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *comp;
|
||||
const char *fixture;
|
||||
const char *substr;
|
||||
} rows[] = {
|
||||
{ "w6c", "test/wcc/data/selfimport.ww", "self-import" },
|
||||
{ "w6c_ww", "test/wcc/data/selfimport.ww", "self-import" },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) { fprintf(stderr, "948 FAIL: getcwd\n"); return 1; }
|
||||
|
||||
for (size_t i = 0; i < sizeof rows / sizeof rows[0]; i++)
|
||||
if (reject(bin, rows[i].comp, rows[i].fixture, rows[i].substr) != 0)
|
||||
return 1;
|
||||
|
||||
printf("self-import rejected by w6c and w6c_ww\n");
|
||||
return 0;
|
||||
}
|
||||
12
test/wcc/data/paramshadowmod/crossmod.ww
Normal file
12
test/wcc/data/paramshadowmod/crossmod.ww
Normal file
@@ -0,0 +1,12 @@
|
||||
// crossmod — sibling module whose probe() takes a param named like the
|
||||
// module `shadowmod` that paramshadowmod (NOT crossmod) imports. The
|
||||
// shadow rule is filtered by the BINDING's own module (src_imports
|
||||
// cur_mod filter): crossmod carries no `import shadowmod`, so this param
|
||||
// must NOT trip even though a sibling module in the same bundle imports
|
||||
// that leaf. Legit-form survivor of the abolished self-import tolerance.
|
||||
|
||||
package crossmod;
|
||||
|
||||
export fn probe(shadowmod: str) i32 = {
|
||||
return shadowmod.len;
|
||||
};
|
||||
17
test/wcc/data/paramshadowmod/pos_crossmod.ww
Normal file
17
test/wcc/data/paramshadowmod/pos_crossmod.ww
Normal file
@@ -0,0 +1,17 @@
|
||||
// pos_crossmod — cur_mod-filtering positive. Replaces the abolished
|
||||
// pos_selfimp (self-import is hard-rejected post-#16). This module
|
||||
// (paramshadowmod) imports `shadowmod`, and the bundle also pulls module
|
||||
// `crossmod`, whose probe() has a param named `shadowmod`. crossmod does
|
||||
// NOT import shadowmod, so the shadow rule — filtered by the binding's
|
||||
// own module — must NOT trip crossmod's param, even though a sibling
|
||||
// module in the same bundle imports that leaf. Build + run; exit = 2.
|
||||
|
||||
package paramshadowmod;
|
||||
|
||||
import shadowmod;
|
||||
import crossmod;
|
||||
|
||||
export fn main() i32 = {
|
||||
let _ = shadowmod.say();
|
||||
return crossmod.probe("hi");
|
||||
};
|
||||
3
test/wcc/data/selfimport.ww
Normal file
3
test/wcc/data/selfimport.ww
Normal file
@@ -0,0 +1,3 @@
|
||||
package foo;
|
||||
import foo;
|
||||
export fn main() int = { return 0; };
|
||||
Reference in New Issue
Block a user