compiler: load imports and enforce package exports
This commit is contained in:
@@ -77,6 +77,7 @@ main(int argc, char **argv)
|
||||
|
||||
check_init(&c, a);
|
||||
c.is_test = testmode;
|
||||
c.sep_mode = sepmode;
|
||||
check_file(&c, file);
|
||||
if (c.errs) return 1;
|
||||
|
||||
|
||||
@@ -543,8 +543,13 @@ wwi_emit(Checker *c, FILE *of, Node *file)
|
||||
k++;
|
||||
}
|
||||
qsort(us, (size_t)nuse, sizeof *us, usecmp);
|
||||
for (int i = 0; i < nuse; i++)
|
||||
const char *previous = NULL;
|
||||
for (int i = 0; i < nuse; i++) {
|
||||
if (previous && strcmp(previous, us[i].path) == 0)
|
||||
continue;
|
||||
fprintf(of, "import %s;\n", us[i].path);
|
||||
previous = us[i].path;
|
||||
}
|
||||
free(us);
|
||||
}
|
||||
|
||||
|
||||
154
cmd/wcc/check.c
154
cmd/wcc/check.c
@@ -59,6 +59,9 @@ lookup_builtin(const char *name)
|
||||
|
||||
static const char *decl_mod(Node *file, Node *d);
|
||||
static const char *use_path(Node *file, const char *curmod, const char *alias);
|
||||
static int src_imports(Node *file, const char *modtag, const char *name);
|
||||
static Sym *lookup_visible(Checker *c, const char *name);
|
||||
static Sym *lookup_visible_type(Checker *c, const char *name);
|
||||
static void resolve_typedecl(Checker *c, Node *d);
|
||||
|
||||
static Type *
|
||||
@@ -69,7 +72,7 @@ resolve_typename(Checker *c, Node *n)
|
||||
if (bi) return bi;
|
||||
/* #225: kind-filtered so a same-named value binding (param/let/fn)
|
||||
* in a closer scope can't hide the type binding it shadows. */
|
||||
Sym *s = scope_lookup_type(c->cur, c->cur_mod, nm);
|
||||
Sym *s = lookup_visible_type(c, nm);
|
||||
if (s == NULL && nm) {
|
||||
/* module-qualified: io.stream → strip the last dot prefix
|
||||
* and look up the leaf, filtering on the importing module's
|
||||
@@ -88,9 +91,9 @@ resolve_typename(Checker *c, Node *n)
|
||||
* import path (symbols are path-keyed). */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
head);
|
||||
if (mk == NULL) mk = head;
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
if (mk != NULL)
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +425,7 @@ assignable_addrfn(Checker *c, Type *dst, Node *rhs)
|
||||
if (rhs->kind != N_UN || rhs->op != TK_AMP) return 0;
|
||||
Node *id = rhs->lhs;
|
||||
if (id == NULL || id->kind != N_IDENT || id->str == NULL) return 0;
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, id->str);
|
||||
Sym *s = lookup_visible(c, id->str);
|
||||
if (s == NULL || s->kind != SK_FN) return 0;
|
||||
Type *fnty = s->type;
|
||||
if (fnty == NULL || fnty->kind != TY_FN) return 0;
|
||||
@@ -606,7 +609,7 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
return 1;
|
||||
}
|
||||
case N_IDENT: {
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
||||
Sym *s = lookup_visible(c, n->str);
|
||||
if (s == NULL || s->kind != SK_DEF ||
|
||||
s->decl == NULL || s->decl->rhs == NULL)
|
||||
return 0;
|
||||
@@ -616,7 +619,7 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
if (n->lhs == NULL || n->lhs->kind != N_IDENT) return 0;
|
||||
/* M1 #22: map the qualifier alias to its dotted import path. */
|
||||
const char *mk = use_path(c->file, c->cur_mod, n->lhs->str);
|
||||
if (mk == NULL) mk = n->lhs->str;
|
||||
if (mk == NULL) return 0;
|
||||
Sym *s = scope_lookup_in_module(c->cur, mk, n->str);
|
||||
if (s == NULL || s->kind != SK_DEF ||
|
||||
s->decl == NULL || s->decl->rhs == NULL)
|
||||
@@ -1414,7 +1417,7 @@ cexpr(Checker *c, Node *n)
|
||||
if (n->str && n->str[0] == '\0')
|
||||
return n->type = err(c, n->pos,
|
||||
"`_` is only valid as a binding or discard lvalue");
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, n->str);
|
||||
Sym *s = lookup_visible(c, n->str);
|
||||
if (s == NULL)
|
||||
return n->type = err(c, n->pos, "undefined: %s", n->str);
|
||||
/* SK_USE has no concrete value type; the only legal use is
|
||||
@@ -1444,8 +1447,7 @@ cexpr(Checker *c, Node *n)
|
||||
* Color sitting at the head of the flat scope chain —
|
||||
* symmetric with wwstage's enumlookup graduation. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *ms = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
Sym *ms = lookup_visible(c, n->lhs->str);
|
||||
if (ms && (ms->kind == SK_USE || ms->use_alias)) {
|
||||
/* Module-qualified ref. `use_alias` covers
|
||||
* the self-import case where the module's
|
||||
@@ -1460,17 +1462,28 @@ cexpr(Checker *c, Node *n)
|
||||
* that path before looking up the leaf. */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
n->lhs->str);
|
||||
if (mk == NULL) mk = n->lhs->str;
|
||||
Sym *fs = scope_lookup_in_module(c->cur,
|
||||
mk, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
if (ms->kind == SK_USE) {
|
||||
/* Pure SK_USE with missing leaf:
|
||||
* external declaration. Codegen
|
||||
* emits CALL/MOVQ by the leaf name
|
||||
* and the linker resolves it. */
|
||||
return n->type = ty_err;
|
||||
if (mk == NULL) {
|
||||
if (ms->kind == SK_USE)
|
||||
return n->type = err(c, n->pos,
|
||||
"package '%s' is not directly imported",
|
||||
n->lhs->str);
|
||||
} else {
|
||||
Sym *fs = scope_lookup_in_module(c->cur,
|
||||
mk, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
/* A bare `w6c -T` intentionally leaves the
|
||||
* compiler-generated test.run hook external; the
|
||||
* ordinary driver supplies lib/test. This is the only
|
||||
* missing direct member that is not a package export
|
||||
* error. */
|
||||
if (c->sep_mode && ms->kind == SK_USE
|
||||
&& n != c->synth_test_run)
|
||||
return n->type = err(c, n->pos,
|
||||
"package '%s' has no exported declaration '%s'",
|
||||
n->lhs->str, n->str);
|
||||
if (ms->kind == SK_USE)
|
||||
return n->type = ty_err;
|
||||
}
|
||||
/* SK_TYPE with use_alias=1 and no leaf
|
||||
* found: fall through so the enum / type-
|
||||
@@ -1616,8 +1629,7 @@ cexpr(Checker *c, Node *n)
|
||||
* def-as-constant splice-index is deferred (no
|
||||
* consumer). */
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
Sym *s = scope_lookup_prefer(c->cur,
|
||||
c->cur_mod, n->lhs->str);
|
||||
Sym *s = lookup_visible(c, n->lhs->str);
|
||||
if (s && s->kind == SK_DEF)
|
||||
return n->type = err(c, n->pos,
|
||||
"cannot index a def-constant str "
|
||||
@@ -1855,7 +1867,7 @@ cexpr(Checker *c, Node *n)
|
||||
* (e.g. lib/os/os.ww) keeps working unchanged. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||
strcmp(n->lhs->str, "abort") == 0 &&
|
||||
scope_lookup_prefer(c->cur, c->cur_mod, "abort") == NULL) {
|
||||
lookup_visible(c, "abort") == NULL) {
|
||||
if (n->list) {
|
||||
Type *mt = cexpr(c, n->list);
|
||||
if (mt != ty_err && !type_assignable(ty_str, mt))
|
||||
@@ -1870,7 +1882,7 @@ cexpr(Checker *c, Node *n)
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
|
||||
strcmp(n->lhs->str, "assert") == 0 &&
|
||||
n->list != NULL &&
|
||||
scope_lookup_prefer(c->cur, c->cur_mod, "assert") == NULL) {
|
||||
lookup_visible(c, "assert") == NULL) {
|
||||
Type *ct = cexpr(c, n->list);
|
||||
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
|
||||
err(c, n->pos, "assert: cond must be bool");
|
||||
@@ -2019,8 +2031,7 @@ cexpr(Checker *c, Node *n)
|
||||
return n->type = ty_void;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str) {
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
Sym *s = lookup_visible(c, n->lhs->str);
|
||||
if (s && s->is_const)
|
||||
err(c, n->pos, "cannot assign to const `%s`",
|
||||
n->lhs->str);
|
||||
@@ -2072,8 +2083,7 @@ cexpr(Checker *c, Node *n)
|
||||
* resolve_type for the synthetic-type-expr case. */
|
||||
Type *t = NULL;
|
||||
if (n->lhs && n->lhs->kind == N_IDENT) {
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod,
|
||||
n->lhs->str);
|
||||
Sym *s = lookup_visible(c, n->lhs->str);
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
t = err(c, n->pos, "unknown struct type '%s'",
|
||||
n->lhs->str);
|
||||
@@ -2865,28 +2875,34 @@ decl_mod(Node *file, Node *d)
|
||||
* concatenated unit may bind the same leaf alias to different paths
|
||||
* (sha256's `import crypto.math` and strconv's `import math` both bind
|
||||
* alias `math`). The import declared in the SAME module as the
|
||||
* reference (`curmod`) is the authoritative one; preferring it closes
|
||||
* the cross-module mis-resolution a first-match scan caused. Falls back
|
||||
* to any matching alias when the referencing module has no own import
|
||||
* (single-occurrence case, unchanged). Returns NULL if no such `use`.
|
||||
* reference (`curmod`) is the authoritative one; requiring it closes both
|
||||
* cross-module mis-resolution and accidental transitive-import visibility.
|
||||
* Returns NULL if the referencing package has no such `use`.
|
||||
*/
|
||||
static const char *
|
||||
use_path(Node *file, const char *curmod, const char *alias)
|
||||
{
|
||||
if (file == NULL || alias == NULL) return NULL;
|
||||
const char *any = NULL;
|
||||
/* Legacy inline multi-package units may spell a package's own
|
||||
* declarations as `pkg.member`. That is self-qualification, not an
|
||||
* imported namespace; preserve it without reopening transitive lookup. */
|
||||
if (curmod != NULL) {
|
||||
const char *dot = strrchr(curmod, '.');
|
||||
const char *leaf = dot ? dot + 1 : curmod;
|
||||
if (strcmp(alias, leaf) == 0) return curmod;
|
||||
}
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->str == NULL
|
||||
|| strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
const char *p = u->usepath ? u->usepath : u->str;
|
||||
int same = (u->module == NULL) ? (curmod == NULL)
|
||||
: (curmod != NULL && strcmp(u->module, curmod) == 0);
|
||||
const char *um = decl_mod(file, u);
|
||||
int same = (um == NULL) ? (curmod == NULL)
|
||||
: (curmod != NULL && strcmp(um, curmod) == 0);
|
||||
if (same)
|
||||
return p;
|
||||
if (any == NULL) any = p;
|
||||
}
|
||||
return any;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2967,6 +2983,67 @@ src_imports(Node *file, const char *modtag, const char *name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A flattened interface symbol is source-visible as a bare name only when
|
||||
* the referencing package directly imports the symbol's defining path. The
|
||||
* strict scope helpers above handle lexical locals, builtins and same-package
|
||||
* declarations first; this second pass restores WW's existing direct-import
|
||||
* convenience without admitting a transitive interface by accident. */
|
||||
static int
|
||||
direct_module_visible(Checker *c, const char *mod)
|
||||
{
|
||||
if (c == NULL || mod == NULL || mod[0] == '\0') return 0;
|
||||
const char *dot = strrchr(mod, '.');
|
||||
const char *alias = dot ? dot + 1 : mod;
|
||||
const char *path = use_path(c->file, c->cur_mod, alias);
|
||||
return path != NULL && strcmp(path, mod) == 0;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
lookup_visible(Checker *c, const char *name)
|
||||
{
|
||||
Sym *s = scope_lookup_prefer(c->cur, c->cur_mod, name);
|
||||
/* A real source declaration shadows a decl-less pseudo-builtin. Keep
|
||||
* the builtin as fallback while checking direct imported declarations. */
|
||||
Sym *builtin = NULL;
|
||||
if (s != NULL) {
|
||||
if (s->decl != NULL) return s;
|
||||
builtin = s;
|
||||
}
|
||||
/* N_USE entries are historically coalesced by leaf in the flat scope,
|
||||
* so the retained symbol may carry another source package's owner. The
|
||||
* source-owned alias map is authoritative: if this package directly
|
||||
* imports NAME, return the coalesced module marker only as a marker; the
|
||||
* N_DOT path maps the alias to the correct full path again. */
|
||||
if (use_path(c->file, c->cur_mod, name) != NULL) {
|
||||
for (Scope *p = c->cur; p; p = p->parent)
|
||||
for (Sym *b = p->first; b; b = b->next)
|
||||
if (strcmp(b->name, name) == 0
|
||||
&& (b->kind == SK_USE || b->use_alias))
|
||||
return b;
|
||||
}
|
||||
for (Scope *p = c->cur; p; p = p->parent) {
|
||||
for (Sym *b = p->first; b; b = b->next)
|
||||
if (strcmp(b->name, name) == 0
|
||||
&& direct_module_visible(c, b->mod))
|
||||
return b;
|
||||
}
|
||||
return builtin;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
lookup_visible_type(Checker *c, const char *name)
|
||||
{
|
||||
Sym *s = scope_lookup_type(c->cur, c->cur_mod, name);
|
||||
if (s != NULL) return s;
|
||||
for (Scope *p = c->cur; p; p = p->parent) {
|
||||
for (Sym *b = p->first; b; b = b->next)
|
||||
if (b->kind == SK_TYPE && strcmp(b->name, name) == 0
|
||||
&& direct_module_visible(c, b->mod))
|
||||
return b;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* check_module_shadow — refuse value bindings that shadow an
|
||||
* in-scope imported module bareword. "Value names and module names
|
||||
@@ -3402,6 +3479,7 @@ check_file(Checker *c, Node *file)
|
||||
dot->lhs = newnode(c->a, N_IDENT, fp);
|
||||
dot->lhs->str = "test";
|
||||
dot->str = "run";
|
||||
c->synth_test_run = dot;
|
||||
call->lhs = dot;
|
||||
call->list = arg;
|
||||
Node *ret = newnode(c->a, N_RETURN, fp);
|
||||
|
||||
180
cmd/wcc/parse.c
180
cmd/wcc/parse.c
@@ -100,6 +100,59 @@ static Node *parsetype(Parser *p);
|
||||
static Node *parseblock(Parser *p);
|
||||
static Node *parsestmt(Parser *p);
|
||||
|
||||
static void
|
||||
skipdecl(Parser *p)
|
||||
{
|
||||
int paren = 0, bracket = 0, brace = 0;
|
||||
while (p->cur.kind != TK_EOF) {
|
||||
switch (p->cur.kind) {
|
||||
case TK_LPAREN: paren++; break;
|
||||
case TK_RPAREN: if (paren > 0) paren--; break;
|
||||
case TK_LBRACK: bracket++; break;
|
||||
case TK_RBRACK: if (bracket > 0) bracket--; break;
|
||||
case TK_LBRACE: brace++; break;
|
||||
case TK_RBRACE: if (brace > 0) brace--; break;
|
||||
case TK_SEMI:
|
||||
advance(p);
|
||||
if (paren == 0 && bracket == 0 && brace == 0)
|
||||
return;
|
||||
continue;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
advance(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* Consume attribute syntax without parsing its expressions or any following
|
||||
* declaration. The imports-only pass needs only to recognize that the next
|
||||
* declaration is an attributed import, which full parsing rejects. */
|
||||
static void
|
||||
skipimportattrs(Parser *p)
|
||||
{
|
||||
while (p->cur.kind == TK_AT) {
|
||||
advance(p);
|
||||
if (p->cur.kind == TK_IDENT)
|
||||
advance(p);
|
||||
else {
|
||||
errorf(p->cur.pos, "expected identifier, got %s",
|
||||
tokname(p->cur.kind));
|
||||
p->errs++;
|
||||
}
|
||||
if (!accept(p, TK_LPAREN)) continue;
|
||||
int depth = 1;
|
||||
while (p->cur.kind != TK_EOF && depth > 0) {
|
||||
if (p->cur.kind == TK_LPAREN) depth++;
|
||||
else if (p->cur.kind == TK_RPAREN) depth--;
|
||||
advance(p);
|
||||
}
|
||||
if (depth > 0) {
|
||||
errorf(p->cur.pos, "expected ')' after attribute");
|
||||
p->errs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Node *
|
||||
parseparams(Parser *p)
|
||||
{
|
||||
@@ -1277,25 +1330,134 @@ parseuse(Parser *p)
|
||||
/* M1 #22: accumulate the full dotted import path (n->module) so the
|
||||
* checker can match decl identity on the path, while n->str stays the
|
||||
* leaf alias the user writes (`utf8.x`). */
|
||||
char pathbuf[256];
|
||||
size_t pl = 0;
|
||||
const char *leaf = expectident(p);
|
||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
||||
pathbuf[pl++] = leaf[i];
|
||||
const char *path = leaf;
|
||||
while (accept(p, TK_DOT)) {
|
||||
leaf = expectident(p);
|
||||
if (pl + 1 < sizeof pathbuf) pathbuf[pl++] = '.';
|
||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
||||
pathbuf[pl++] = leaf[i];
|
||||
path = aprintf(p->a, "%s.%s", path, leaf);
|
||||
}
|
||||
pathbuf[pl] = '\0';
|
||||
n->str = leaf;
|
||||
n->strlen = strlen(leaf);
|
||||
n->usepath = astrndup(p->a, pathbuf, pl);
|
||||
n->usepath = path;
|
||||
expect(p, TK_SEMI);
|
||||
return n;
|
||||
}
|
||||
|
||||
Node *
|
||||
parseimports(Parser *p)
|
||||
{
|
||||
Pos fp = { p->l->file, 1, 1 };
|
||||
Node *file = newnode(p->a, N_FILE, fp);
|
||||
Node *head = NULL, *tail = NULL;
|
||||
Node *packages = NULL, *packagetail = NULL;
|
||||
int sawpackage = 0;
|
||||
|
||||
while (p->cur.kind != TK_EOF) {
|
||||
/* Compiler/driver bundle markers carry package identity out of
|
||||
* band. They are not source declarations, so keep scanning for
|
||||
* the following package clause and imports. */
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
sawpackage = 0;
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = NULL;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
const char *rp = p->cur.text;
|
||||
sawpackage = 0;
|
||||
advance(p);
|
||||
p->pathmod = NULL;
|
||||
p->curmod = rp;
|
||||
p->resetmod = rp;
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_MODULE) {
|
||||
Pos pp = p->cur.pos;
|
||||
advance(p);
|
||||
if (p->cur.kind != TK_IDENT) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
sawpackage = 1;
|
||||
skipdecl(p);
|
||||
continue;
|
||||
}
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curmod = name;
|
||||
Node *package = newnode(p->a, N_FILE, pp);
|
||||
package->module = name;
|
||||
if (packages == NULL)
|
||||
packages = package;
|
||||
else
|
||||
packagetail->next = package;
|
||||
packagetail = package;
|
||||
if (!sawpackage) {
|
||||
file->module = name;
|
||||
file->pos = pp;
|
||||
sawpackage = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!sawpackage && p->pathmod == NULL && p->resetmod == NULL) {
|
||||
errorf(p->cur.pos, "invalid or missing package clause");
|
||||
p->errs++;
|
||||
sawpackage = 1;
|
||||
}
|
||||
if (p->cur.kind == TK_USE) {
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
tail->next = d;
|
||||
tail = d;
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_AT) {
|
||||
skipimportattrs(p);
|
||||
if (p->cur.kind == TK_EXPORT) advance(p);
|
||||
if (p->cur.kind == TK_USE) {
|
||||
errorf(p->cur.pos,
|
||||
"import cannot be exported or attributed");
|
||||
p->errs++;
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
tail->next = d;
|
||||
tail = d;
|
||||
continue;
|
||||
}
|
||||
skipdecl(p);
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_EXPORT && peek(p).kind == TK_USE) {
|
||||
advance(p);
|
||||
errorf(p->cur.pos,
|
||||
"import cannot be exported or attributed");
|
||||
p->errs++;
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
tail->next = d;
|
||||
tail = d;
|
||||
continue;
|
||||
}
|
||||
skipdecl(p);
|
||||
}
|
||||
file->list = head;
|
||||
/* The package-clause chain lets a directory loader validate every
|
||||
* selected file without inventing a second header grammar. It lives in
|
||||
* body because list is the public imports chain. */
|
||||
file->body = packages;
|
||||
return file;
|
||||
}
|
||||
|
||||
static Node *
|
||||
parsedef(Parser *p, int exp)
|
||||
{
|
||||
|
||||
@@ -68,8 +68,10 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
||||
|
||||
/*
|
||||
* Within each scope's bucket: Pass 1 prefers entries whose `sym.mod`
|
||||
* matches the caller's `mod`; Pass 2 falls back to the first match
|
||||
* regardless of mod (the existing scope_lookup semantics). We only
|
||||
* matches the caller's `mod`; Pass 2 accepts only a source-visible bare
|
||||
* entry. For a primary package that means mod==NULL. For an imported
|
||||
* package it means a lexical local (a child scope) or a decl-less builtin,
|
||||
* never another package's flattened interface value. We only
|
||||
* descend to the parent scope when the current scope has no matching
|
||||
* entry at all — so a local binding in a closer scope still shadows a
|
||||
* same-name fn from a parent scope, even when the parent entry
|
||||
@@ -83,14 +85,16 @@ scope_lookup_in_module(Scope *s, const char *mod, const char *name)
|
||||
Sym *
|
||||
scope_lookup_prefer(Scope *s, const char *mod, const char *name)
|
||||
{
|
||||
if (mod == NULL) return scope_lookup(s, name);
|
||||
for (Scope *p = s; p; p = p->parent) {
|
||||
u64 h = hashstr(name) % p->nbuckets;
|
||||
Sym *fallback = NULL;
|
||||
for (Sym *b = p->buckets[h]; b; b = b->hashnext) {
|
||||
if (strcmp(b->name, name) != 0) continue;
|
||||
if (b->mod && strcmp(b->mod, mod) == 0) return b;
|
||||
if (fallback == NULL) fallback = b;
|
||||
if (mod && b->mod && strcmp(b->mod, mod) == 0)
|
||||
return b;
|
||||
if (b->mod == NULL && (mod == NULL || p->parent != NULL
|
||||
|| b->decl == NULL) && fallback == NULL)
|
||||
fallback = b;
|
||||
}
|
||||
if (fallback) return fallback;
|
||||
}
|
||||
@@ -115,7 +119,9 @@ scope_lookup_type(Scope *s, const char *mod, const char *name)
|
||||
if (b->kind != SK_TYPE) continue;
|
||||
if (strcmp(b->name, name) != 0) continue;
|
||||
if (mod && b->mod && strcmp(b->mod, mod) == 0) return b;
|
||||
if (fallback == NULL) fallback = b;
|
||||
if (b->mod == NULL && (mod == NULL || b->decl == NULL)
|
||||
&& fallback == NULL)
|
||||
fallback = b;
|
||||
}
|
||||
if (fallback) return fallback;
|
||||
}
|
||||
|
||||
@@ -383,6 +383,9 @@ struct Parser {
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
Node *parsefile(Parser*);
|
||||
/* Imports-only N_FILE: module/pos identify the first package clause,
|
||||
* list holds N_USE declarations, and body holds package-clause markers. */
|
||||
Node *parseimports(Parser*);
|
||||
Node *parseexpr_top(Parser*); /* for testing: parse one expression */
|
||||
|
||||
typedef enum {
|
||||
@@ -585,6 +588,10 @@ struct Checker {
|
||||
int errs;
|
||||
int is_test; /* #15: `w6c -T` — collect @test fns + synth
|
||||
* the entry; loud-reject a user main. */
|
||||
int sep_mode; /* -c package compilation: imported interfaces are
|
||||
* present, so absent members are hard export errors. */
|
||||
Node *synth_test_run; /* exact compiler-generated test.run DOT;
|
||||
* its unresolved external hook is intentional */
|
||||
Node *alloc_octx; /* #3/B': the one empty `alloc([], n)` call node
|
||||
* that has let-declared slice context this walk;
|
||||
* any OTHER empty alloc has no element-type hint
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// (the package, not the file, is the unit of testing).
|
||||
package strings_test;
|
||||
|
||||
import bytes;
|
||||
import strings;
|
||||
import os;
|
||||
import test;
|
||||
|
||||
@@ -77,9 +77,26 @@ fn accepttok(p: *parser, k: tkind) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn putdec(v: i32) void = {
|
||||
let digits: [16]u8;
|
||||
let n: i32 = 0;
|
||||
let x: i32 = v;
|
||||
if (x <= 0) { digits[n] = '0'; n += 1; }
|
||||
else {
|
||||
for (x > 0) {
|
||||
digits[n] = ((x % 10) + ('0': i32)): u8;
|
||||
n += 1;
|
||||
x = x / 10;
|
||||
};
|
||||
};
|
||||
for (n > 0) { n -= 1; os.write(2, &digits[n], 1u64); };
|
||||
};
|
||||
|
||||
fn errmsg(p: *parser, msg: str) void = {
|
||||
let pre = "parse: ";
|
||||
os.write(2, pre.ptr, pre.len: u64);
|
||||
os.write(2, p.curfile.ptr, p.curfile.len: u64);
|
||||
os.write(2, ":".ptr, 1u64); putdec(p.curline);
|
||||
os.write(2, ":".ptr, 1u64); putdec(p.curcol);
|
||||
os.write(2, ": error: ".ptr, 9u64);
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.write(2, "\n".ptr, 1u64);
|
||||
p.errs += 1;
|
||||
@@ -91,11 +108,12 @@ fn expecttok(p: *parser, k: tkind, what: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Returns the empty str on error (and advances to make progress).
|
||||
// Returns false without consuming the unexpected token; the enclosing
|
||||
// production owns recovery, matching the C frontend.
|
||||
fn expectident(p: *parser, into: *str) bool = {
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
errmsg(p, "expected identifier");
|
||||
advance(p);
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
tokname(p.curkind)));
|
||||
return false;
|
||||
};
|
||||
*into = p.curtext;
|
||||
@@ -413,6 +431,153 @@ fn isassignop(k: tkind) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
fn skipimportdecl(p: *parser) void = {
|
||||
let paren: i32 = 0;
|
||||
let bracket: i32 = 0;
|
||||
let brace: i32 = 0;
|
||||
for (p.curkind != tkind.TK_EOF) {
|
||||
if (p.curkind == tkind.TK_LPAREN) { paren += 1; }
|
||||
else { if (p.curkind == tkind.TK_RPAREN) {
|
||||
if (paren > 0) { paren -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_LBRACK) { bracket += 1; }
|
||||
else { if (p.curkind == tkind.TK_RBRACK) {
|
||||
if (bracket > 0) { bracket -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_LBRACE) { brace += 1; }
|
||||
else { if (p.curkind == tkind.TK_RBRACE) {
|
||||
if (brace > 0) { brace -= 1; };
|
||||
} else { if (p.curkind == tkind.TK_SEMI) {
|
||||
advance(p);
|
||||
if (paren == 0 && bracket == 0 && brace == 0) { return; };
|
||||
continue;
|
||||
};};};};};};};
|
||||
advance(p);
|
||||
};
|
||||
};
|
||||
|
||||
// Consume attributes without parsing their argument expressions. The
|
||||
// imports-only pass only needs to recognize and reject an attributed import.
|
||||
fn skipimportattrs(p: *parser) void = {
|
||||
for (p.curkind == tkind.TK_AT) {
|
||||
advance(p);
|
||||
if (p.curkind == tkind.TK_IDENT) { advance(p); }
|
||||
else {
|
||||
errmsg(p, strings.concat("expected identifier, got ",
|
||||
tokname(p.curkind)));
|
||||
};
|
||||
if (p.curkind == tkind.TK_LPAREN) {
|
||||
advance(p);
|
||||
let depth: i32 = 1;
|
||||
for (p.curkind != tkind.TK_EOF && depth > 0) {
|
||||
if (p.curkind == tkind.TK_LPAREN) { depth += 1; }
|
||||
else { if (p.curkind == tkind.TK_RPAREN) { depth -= 1; }; };
|
||||
advance(p);
|
||||
};
|
||||
if (depth > 0) { errmsg(p, "expected ')' after attribute"); };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export fn parseimports(p: *parser) *node = {
|
||||
let f = newnode(nkind.N_FILE, p.curfile, 1, 1);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
let packages: *node = nil;
|
||||
let packagetail: *node = nil;
|
||||
let sawpackage: bool = false;
|
||||
for (p.curkind != tkind.TK_EOF) {
|
||||
// Compiler/driver bundle markers carry package identity out of
|
||||
// band. They are not source declarations, so keep scanning for
|
||||
// the following package clause and imports.
|
||||
if (p.curkind == tkind.TK_MODPATH) {
|
||||
sawpackage = false;
|
||||
p.pathmod = p.curtext;
|
||||
p.curmod = p.curtext;
|
||||
p.resetmod = "";
|
||||
advance(p);
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_MODRESET) {
|
||||
let rp: str = p.curtext;
|
||||
sawpackage = false;
|
||||
advance(p);
|
||||
p.pathmod = "";
|
||||
p.curmod = rp;
|
||||
p.resetmod = rp;
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_MODULE) {
|
||||
let pf: str = p.curfile;
|
||||
let pl: i32 = p.curline;
|
||||
let pc: i32 = p.curcol;
|
||||
advance(p);
|
||||
if (p.curkind != tkind.TK_IDENT) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
sawpackage = true;
|
||||
skipimportdecl(p);
|
||||
continue;
|
||||
};
|
||||
let name: str;
|
||||
expectident(p, &name);
|
||||
expecttok(p, tkind.TK_SEMI, "expected ';' after module name");
|
||||
p.curmod = name;
|
||||
let pm = newnode(nkind.N_FILE, pf, pl, pc);
|
||||
pm.nmod = name;
|
||||
if (packages == nil) { packages = pm; }
|
||||
else { packagetail.next = pm; };
|
||||
packagetail = pm;
|
||||
if (!sawpackage) {
|
||||
f.nmod = name;
|
||||
f.file = pf;
|
||||
f.line = pl;
|
||||
f.col = pc;
|
||||
sawpackage = true;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
if (!sawpackage && p.pathmod.len == 0 && p.resetmod.len == 0) {
|
||||
errmsg(p, "invalid or missing package clause");
|
||||
sawpackage = true;
|
||||
};
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_AT) {
|
||||
skipimportattrs(p);
|
||||
if (p.curkind == tkind.TK_EXPORT) { advance(p); };
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
skipimportdecl(p);
|
||||
continue;
|
||||
};
|
||||
if (p.curkind == tkind.TK_EXPORT) {
|
||||
advance(p);
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
let d: *node = parseuse(p);
|
||||
d.nmod = p.curmod;
|
||||
if (head == nil) { head = d; } else { tail.next = d; };
|
||||
tail = d;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
skipimportdecl(p);
|
||||
};
|
||||
f.list = head;
|
||||
// body carries package-clause markers; list remains imports only.
|
||||
f.body = packages;
|
||||
return f;
|
||||
};
|
||||
|
||||
// Forward references between parseunary/parseexpr/parsebin/parsepostfix
|
||||
// are resolved by the two-pass checker — no body-less prototypes needed.
|
||||
|
||||
@@ -523,6 +688,9 @@ export fn parsefile(p: *parser) *node = {
|
||||
|
||||
let d: *node = nil;
|
||||
if (p.curkind == tkind.TK_USE) {
|
||||
if (attrs != nil || exported != 0) {
|
||||
errmsg(p, "import cannot be exported or attributed");
|
||||
};
|
||||
d = parseuse(p);
|
||||
} else { if (p.curkind == tkind.TK_DEF) {
|
||||
d = parsedef(p, exported);
|
||||
|
||||
@@ -110,11 +110,9 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
||||
// scopelookupinmodule(c, leaf, leaf) won't find it.
|
||||
//
|
||||
// #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.
|
||||
// matches `mod`; Pass-2 accepts only a primary-package or builtin type. The
|
||||
// synthesized global `nomem` has an empty source location and is builtin too.
|
||||
// Flattened transitive interface types are never an unqualified fallback.
|
||||
// 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
|
||||
@@ -136,7 +134,12 @@ export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = {
|
||||
};
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
let builtin: bool = b.decl == nil;
|
||||
if (!builtin && b.decl.file.len == 0 && b.decl.line == 0) {
|
||||
builtin = true;
|
||||
};
|
||||
if (b.mod.len == 0 && (mod.len == 0 || builtin)
|
||||
&& fallback == nil) { fallback = b; };
|
||||
};
|
||||
};
|
||||
b = b.hashnext;
|
||||
@@ -220,8 +223,10 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
};
|
||||
|
||||
// Within each scope's bucket: Pass 1 prefers entries whose
|
||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||
// regardless of mod (same semantics as scopelookup). We only descend
|
||||
// `sym.mod` matches `mod`; Pass 2 accepts only a source-visible bare entry.
|
||||
// For an imported package that means a lexical local (child scope) or a
|
||||
// decl-less builtin, never another package's flattened interface symbol.
|
||||
// We only descend
|
||||
// to the parent scope when the current scope has no matching entry at
|
||||
// all — so a local binding in a closer scope still shadows a same-name
|
||||
// fn from a parent scope, even when the parent entry mod-matches.
|
||||
@@ -234,7 +239,6 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
|
||||
// io.read that happens to hash earlier into the flat scope. Mirrors
|
||||
// cmd/wcc/sym.c scope_lookup_prefer.
|
||||
export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
if (mod.len == 0) { return scopelookup(s, name); };
|
||||
let p: *scope = s;
|
||||
for (p != nil) {
|
||||
let h: u64 = hashstr(name);
|
||||
@@ -243,12 +247,13 @@ export fn scopelookupprefer(s: *scope, mod: str, name: str) *sym = {
|
||||
let fallback: *sym = nil;
|
||||
for (b != nil) {
|
||||
if (streq(b.name, name)) {
|
||||
if (b.mod.len > 0) {
|
||||
if (mod.len > 0 && b.mod.len > 0) {
|
||||
if (streq(b.mod, mod)) {
|
||||
return b;
|
||||
};
|
||||
};
|
||||
if (fallback == nil) { fallback = b; };
|
||||
if (b.mod.len == 0 && (mod.len == 0 || p.parent != nil
|
||||
|| b.decl == nil) && fallback == nil) { fallback = b; };
|
||||
};
|
||||
b = b.hashnext;
|
||||
};
|
||||
|
||||
@@ -172,6 +172,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
let ck: checker;
|
||||
checkinit(&ck, &tc);
|
||||
ck.istest = testmode;
|
||||
ck.sepmode = sepmode;
|
||||
checkfile(&ck, f);
|
||||
if (ck.errs > 0) { return 1; };
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package wcc;
|
||||
import os;
|
||||
import syntax;
|
||||
import strconv;
|
||||
import strings;
|
||||
|
||||
type checker = struct {
|
||||
tc: *syntax.tctx,
|
||||
@@ -19,6 +20,10 @@ type checker = struct {
|
||||
// twin (c->matcharms)
|
||||
istest: i32, // #15: `w6c_ww -T` — collect @test fns +
|
||||
// synth the entry; loud-reject a user main.
|
||||
sepmode: i32, // -c package compilation: imported interfaces are
|
||||
// present, so absent members are hard export errors.
|
||||
synthtestrun: *syntax.node, // exact generated test.run DOT; its
|
||||
// unresolved external hook is intentional
|
||||
verbose: i32, // when non-zero, log each unresolved name
|
||||
fnret: *syntax.node, // enclosing fn's return type AST (for `?`)
|
||||
curmod: str, // importing-module bareword for the decl
|
||||
@@ -164,17 +169,31 @@ fn declmod(file: *syntax.node, d: *syntax.node) str = {
|
||||
// to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
// module-qualified resolution and codegen hint (M1 #22). Single-level
|
||||
// packages have usepath == alias so the result is unchanged. The
|
||||
// current tree has one occurrence per leaf, so the map is unambiguous.
|
||||
fn usepathfor(file: *syntax.node, alias: str) str = {
|
||||
// Only an import owned by the referencing package is visible; a matching
|
||||
// alias carried by a transitive interface is deliberately ignored.
|
||||
fn usepathfor(file: *syntax.node, modtag: str, alias: str) str = {
|
||||
let empty: str;
|
||||
if (file == nil) { return empty; };
|
||||
if (alias.len == 0) { return empty; };
|
||||
if (modtag.len != 0) {
|
||||
let (prefix, suffix) = strings.rcut(modtag, ".");
|
||||
let leaf: str = suffix;
|
||||
if (leaf.len == 0) { leaf = modtag; };
|
||||
if (syntax.streq(alias, leaf)) { return modtag; };
|
||||
};
|
||||
let u: *syntax.node = file.list;
|
||||
for (u != nil) {
|
||||
if (u.kind == syntax.nkind.N_USE) {
|
||||
if (syntax.streq(u.str, alias)) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
let um: str = declmod(file, u);
|
||||
let same: bool = false;
|
||||
if (modtag.len == 0) {
|
||||
if (um.len == 0) { same = true; };
|
||||
} else { if (syntax.streq(um, modtag)) { same = true; }; };
|
||||
if (same) {
|
||||
if (u.usepath.len != 0) { return u.usepath; };
|
||||
return u.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
u = u.next;
|
||||
@@ -182,12 +201,10 @@ fn usepathfor(file: *syntax.node, alias: str) str = {
|
||||
return empty;
|
||||
};
|
||||
|
||||
// modkeyfor — usepathfor with leaf-alias fallback: the module key for a
|
||||
// path-keyed scopelookupinmodule given the alias the user wrote (M1 #22).
|
||||
// modkeyfor — the module key for a directly imported alias. Empty means
|
||||
// the referencing package did not itself declare that import.
|
||||
fn modkeyfor(c: *checker, alias: str) str = {
|
||||
let mk: str = usepathfor(c.file, alias);
|
||||
if (mk.len == 0) { return alias; };
|
||||
return mk;
|
||||
return usepathfor(c.file, c.curmod, alias);
|
||||
};
|
||||
|
||||
// srcimports — does the source file that contributed decl-module
|
||||
@@ -225,6 +242,132 @@ fn srcimports(file: *syntax.node, modtag: str, name: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Bare imported declarations remain WW source syntax, but only across a
|
||||
// direct edge. The syntax scope helpers resolve lexical locals, builtins and
|
||||
// same-package declarations first; this pass admits a flattened interface
|
||||
// symbol iff the referencing source package itself imported its module path.
|
||||
fn directmodvisible(c: *checker, mod: str) bool = {
|
||||
if (mod.len == 0) { return false; };
|
||||
let (prefix, suffix) = strings.rcut(mod, ".");
|
||||
let alias: str = suffix;
|
||||
if (alias.len == 0) { alias = mod; };
|
||||
let path: str = usepathfor(c.file, c.curmod, alias);
|
||||
return path.len != 0 && syntax.streq(path, mod);
|
||||
};
|
||||
|
||||
fn lookupvisible(c: *checker, name: str) *syntax.sym = {
|
||||
let found: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, name);
|
||||
let builtin: *syntax.sym = nil;
|
||||
if (found != nil) {
|
||||
if (found.decl != nil) { return found; };
|
||||
builtin = found;
|
||||
};
|
||||
// Flat scope installation coalesces same-leaf N_USE entries. The
|
||||
// source-owned alias map, not the retained marker's mod field, decides
|
||||
// whether this package can use the qualifier.
|
||||
if (usepathfor(c.file, c.curmod, name).len != 0) {
|
||||
let q: *syntax.scope = c.cur;
|
||||
for (q != nil) {
|
||||
let u: *syntax.sym = q.first;
|
||||
for (u != nil) {
|
||||
if (syntax.streq(u.name, name)
|
||||
&& (u.skind == syntax.skind.SK_USE
|
||||
|| u.use_alias != 0i32)) { return u; };
|
||||
u = u.snext;
|
||||
};
|
||||
q = q.parent;
|
||||
};
|
||||
};
|
||||
let p: *syntax.scope = c.cur;
|
||||
for (p != nil) {
|
||||
let b: *syntax.sym = p.first;
|
||||
for (b != nil) {
|
||||
if (syntax.streq(b.name, name) && directmodvisible(c, b.mod)) {
|
||||
return b;
|
||||
};
|
||||
b = b.snext;
|
||||
};
|
||||
p = p.parent;
|
||||
};
|
||||
return builtin;
|
||||
};
|
||||
|
||||
fn lookupvisibletype(c: *checker, name: str) *syntax.sym = {
|
||||
// C resolves intrinsic type names before consulting package symbols.
|
||||
// Select the empty-module seed here so an imported interface cannot
|
||||
// redefine a builtin for wwstage; size/opaque have no seed and remain
|
||||
// handled directly by tinfofornode.
|
||||
if (builtintypename(name)) {
|
||||
let empty: str;
|
||||
return syntax.scopelookuptype(c.cur, empty, name);
|
||||
};
|
||||
let found: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, name);
|
||||
if (found != nil) { return found; };
|
||||
let p: *syntax.scope = c.cur;
|
||||
for (p != nil) {
|
||||
let b: *syntax.sym = p.first;
|
||||
for (b != nil) {
|
||||
if (b.skind == syntax.skind.SK_TYPE
|
||||
&& syntax.streq(b.name, name)
|
||||
&& directmodvisible(c, b.mod)) { return b; };
|
||||
b = b.snext;
|
||||
};
|
||||
p = p.parent;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
fn builtintypename(name: str) bool = {
|
||||
return syntax.streq(name, "void")
|
||||
|| syntax.streq(name, "bool")
|
||||
|| syntax.streq(name, "rune")
|
||||
|| syntax.streq(name, "i8")
|
||||
|| syntax.streq(name, "i16")
|
||||
|| syntax.streq(name, "i32")
|
||||
|| syntax.streq(name, "i64")
|
||||
|| syntax.streq(name, "u8")
|
||||
|| syntax.streq(name, "u16")
|
||||
|| syntax.streq(name, "u32")
|
||||
|| syntax.streq(name, "u64")
|
||||
|| syntax.streq(name, "int")
|
||||
|| syntax.streq(name, "uint")
|
||||
|| syntax.streq(name, "uintptr")
|
||||
|| syntax.streq(name, "size")
|
||||
|| syntax.streq(name, "opaque")
|
||||
|| syntax.streq(name, "f32")
|
||||
|| syntax.streq(name, "f64")
|
||||
|| syntax.streq(name, "str")
|
||||
|| syntax.streq(name, "never")
|
||||
|| syntax.streq(name, "nomem")
|
||||
|| syntax.streq(name, "untyped_int")
|
||||
|| syntax.streq(name, "untyped_float")
|
||||
|| syntax.streq(name, "untyped_str")
|
||||
|| syntax.streq(name, "untyped_rune")
|
||||
|| syntax.streq(name, "untyped_bool")
|
||||
|| syntax.streq(name, "untyped_nil");
|
||||
};
|
||||
|
||||
fn packageaccesserr(c: *checker, e: *syntax.node, pkg: str, member: str,
|
||||
missing: bool) void = {
|
||||
cerr(e.file); cerr(":");
|
||||
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(e.col, strconv.base.DEC));
|
||||
cerr(": error: package '"); cerr(pkg);
|
||||
if (missing) {
|
||||
cerr("' has no exported declaration '"); cerr(member); cerr("'\n");
|
||||
} else {
|
||||
cerr("' is not directly imported\n");
|
||||
};
|
||||
c.errs += 1;
|
||||
};
|
||||
|
||||
// A bare `w6c -T` leaves the compiler-generated test.run hook external;
|
||||
// the ordinary driver supplies lib/test. No user-written missing member is
|
||||
// exempt from package export checking.
|
||||
fn synthesizedtestrun(c: *checker, e: *syntax.node) bool = {
|
||||
return c.synthtestrun != nil && c.synthtestrun == e;
|
||||
};
|
||||
|
||||
// checkmoduleshadow — enforce "value names and module names are
|
||||
// disjoint" at nested-scope binds. Mirrors cstage check_module_shadow
|
||||
// (cmd/wcc/check.c). Fires for fn params / lets / forrange iters /
|
||||
@@ -498,7 +641,7 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (k == syntax.nkind.N_IDENT) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
||||
let s: *syntax.sym = lookupvisible(c, nm);
|
||||
if (s == nil) {
|
||||
// Unshadowed abort/assert binds no sym BY
|
||||
// DESIGN (the EXPR_ASSERT family has no callee
|
||||
@@ -522,13 +665,14 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
if (k == syntax.nkind.N_TNAME) {
|
||||
let nm: str = n.str;
|
||||
if (nm.len > 0) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
||||
let s: *syntax.sym = lookupvisibletype(c, nm);
|
||||
let builtin: bool = builtintypename(nm);
|
||||
// `pkg.Type` — strip the last dot prefix and look up
|
||||
// the leaf with a mod filter so same-leaf-name types
|
||||
// from different imports (`bufio.stream` vs
|
||||
// `io.stream`) disambiguate to the right one.
|
||||
// Mirrors cmd/wcc/check.c resolve_typename.
|
||||
if (s == nil) {
|
||||
if (s == nil && !builtin) {
|
||||
let dot: i32 = nm.len - 1;
|
||||
for (dot >= 0) {
|
||||
if (nm[dot] == 46u8) { break; };
|
||||
@@ -543,18 +687,39 @@ fn resolvewalk(c: *checker, n: *syntax.node) void = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + (dot + 1): u64;
|
||||
leaf.len = nm.len - (dot + 1);
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
let mk: str = modkeyfor(c, head);
|
||||
if (mk.len != 0) {
|
||||
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (s == nil) {
|
||||
if (s == nil && !builtin) {
|
||||
if (syntax.scopelookup(c.cur, nm) != nil) {
|
||||
cerr(n.file); cerr(":");
|
||||
cerr(strconv.i32tos(n.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(n.col, strconv.base.DEC));
|
||||
cerr(": error: unknown type '"); cerr(nm); cerr("'\n");
|
||||
c.errs += 1;
|
||||
n.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved tname: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
};
|
||||
} else { c.nresolved += 1; };
|
||||
} else {
|
||||
c.nresolved += 1;
|
||||
// Resolve and cache the type while c.curmod still names the
|
||||
// declaration that owns this syntax node. Exported structs may
|
||||
// contain a direct dependency's type; consumers need that cached
|
||||
// shape for structural field access, but must not gain source
|
||||
// visibility of the dependency qualifier. Cstage does the same
|
||||
// owner-scoped work in resolve_typedecl.
|
||||
let ti: *syntax.tinfo = tinfofornode(c, n);
|
||||
if (ti != nil) { n.type_ = ti: *void; };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -991,6 +1156,25 @@ fn unwrapbang(n: *syntax.node) *syntax.node = {
|
||||
fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
||||
if (n == nil) { return nil; };
|
||||
if (n.kind != syntax.nkind.N_TNAME) { return nil; };
|
||||
// An exported signature is resolved while its owning package is the
|
||||
// current module. Preserve that compiler-owned binding when the exact
|
||||
// same AST node is later inspected structurally by a consumer (for
|
||||
// example `os.filestat.atime.sec`, where atime is time.instant). This
|
||||
// does not make a consumer-written `time.instant` visible: such a node
|
||||
// has no owner-resolved named cache unless the consumer imports time.
|
||||
let bound: *syntax.tinfo = n.type_: *syntax.tinfo;
|
||||
if (bound != nil) { if (bound.kind == syntax.tykind.TY_NAMED) {
|
||||
let owner: *syntax.scope = c.cur;
|
||||
for (owner != nil) {
|
||||
let bs: *syntax.sym = owner.first;
|
||||
for (bs != nil) {
|
||||
if (bs.skind == syntax.skind.SK_TYPE
|
||||
&& bs.type_ == bound) { return bs; };
|
||||
bs = bs.snext;
|
||||
};
|
||||
owner = owner.parent;
|
||||
};
|
||||
}; };
|
||||
let nm: str = n.str;
|
||||
// #51: pkg.alias type refs land here as a single TNAME whose
|
||||
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
|
||||
@@ -1013,7 +1197,10 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
||||
let leaf: str;
|
||||
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
|
||||
leaf.len = nm.len - dotidx - 1;
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, head), leaf);
|
||||
let mk: str = modkeyfor(c, head);
|
||||
if (mk.len != 0) {
|
||||
s = syntax.scopelookupinmodule(c.cur, mk, leaf);
|
||||
};
|
||||
} else {
|
||||
// #53: same-module preference. Mirrors cstage
|
||||
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
|
||||
@@ -1026,7 +1213,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
||||
// 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 = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
||||
s = lookupvisibletype(c, nm);
|
||||
// #61 A.5: bare TNAME that collides with an imported
|
||||
// module bareword. Two shapes hit this:
|
||||
// - `let l: lex;` where `lex` struct lives in
|
||||
@@ -1049,7 +1236,7 @@ fn aliassym(c: *checker, n: *syntax.node) *syntax.sym = {
|
||||
// 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: *syntax.sym = syntax.scopelookuptype(c.cur, c.curmod, nm);
|
||||
let sm: *syntax.sym = lookupvisibletype(c, nm);
|
||||
if (sm != nil) { s = sm; };
|
||||
};
|
||||
};
|
||||
@@ -1271,7 +1458,9 @@ fn scruttype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
if (e.kind == syntax.nkind.N_DOT) {
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind != syntax.nkind.N_IDENT) { return nil; };
|
||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.str), e.str);
|
||||
let mk: str = modkeyfor(c, e.lhs.str);
|
||||
if (mk.len == 0) { return nil; };
|
||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
@@ -1862,7 +2051,7 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
return true;
|
||||
};
|
||||
if (k == syntax.nkind.N_IDENT) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.str);
|
||||
let s: *syntax.sym = lookupvisible(c, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -1872,7 +2061,9 @@ fn evaldefconst(c: *checker, n: *syntax.node, out: *u64, depth: i32) bool = {
|
||||
if (k == syntax.nkind.N_DOT) {
|
||||
if (n.lhs == nil) { return false; };
|
||||
if (n.lhs.kind != syntax.nkind.N_IDENT) { return false; };
|
||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, n.lhs.str), n.str);
|
||||
let mk: str = modkeyfor(c, n.lhs.str);
|
||||
if (mk.len == 0) { return false; };
|
||||
let s: *syntax.sym = syntax.scopelookupinmodule(c.cur, mk, n.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != syntax.skind.SK_DEF) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -3287,7 +3478,7 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// the foreign signature (scopedefineinmodule prepends);
|
||||
// cstage types a fn ident via scope_lookup_prefer with
|
||||
// cur_mod (cmd/wcc/check.c:1305).
|
||||
let fs: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
||||
let fs: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||
if (fs != nil) {
|
||||
if (fs.skind == syntax.skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
@@ -3313,7 +3504,11 @@ fn unoptype(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// cstage's actual acceptance reason.
|
||||
if (e.lhs.kind == syntax.nkind.N_DOT) {
|
||||
if (e.lhs.lhs != nil && e.lhs.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, e.lhs.lhs.str), e.lhs.str);
|
||||
let fs: *syntax.sym = nil;
|
||||
let mk: str = modkeyfor(c, e.lhs.lhs.str);
|
||||
if (mk.len != 0) {
|
||||
fs = syntax.scopelookupinmodule(c.cur, mk, e.lhs.str);
|
||||
};
|
||||
if (fs != nil) {
|
||||
if (fs.skind == syntax.skind.SK_FN) {
|
||||
if (fs.decl != nil) {
|
||||
@@ -3388,7 +3583,7 @@ fn indexresult(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// valid; only the SK_DEF scalar-str operand is
|
||||
// unindexable. cstage twin: check.c N_INDEX TY_STR arm.
|
||||
if (e.lhs != nil && e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
||||
let s: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||
if (s != nil && s.skind == syntax.skind.SK_DEF) {
|
||||
cerr("error: cannot index a def-constant str '");
|
||||
cerr(e.lhs.str);
|
||||
@@ -3536,8 +3731,22 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// bare `error` then binds strconv.error not io.error). Mirrors
|
||||
// cstage cmd/wcc/check.c:66 scope_lookup_prefer; sibling #56 at
|
||||
// L2439, #53 at L688. Tracked in the cluster note at L685-687.
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
let s: *syntax.sym = lookupvisible(c, e.str);
|
||||
if (s == nil) {
|
||||
// A same-named flattened symbol that fails lookupvisible is a
|
||||
// transitive implementation fact, not an unresolved external.
|
||||
// Diagnose it like cstage's N_IDENT path and stamp tyerr so
|
||||
// later call checking does not obscure the causal error.
|
||||
if (syntax.scopelookup(c.cur, e.str) != nil) {
|
||||
cerr(e.file); cerr(":");
|
||||
cerr(strconv.i32tos(e.line, strconv.base.DEC)); cerr(":");
|
||||
cerr(strconv.i32tos(e.col, strconv.base.DEC));
|
||||
cerr(": error: undefined: "); cerr(e.str); cerr("\n");
|
||||
c.errs += 1;
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
if (s.decl == nil) { return nil; };
|
||||
// #34: a bare fn-name rvalue types as its FN TYPE, not its return
|
||||
// type. decl.lhs is the RETURN type for an N_FNDECL, so synthesize
|
||||
@@ -3632,6 +3841,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
return e.rhs;
|
||||
};
|
||||
if (k == syntax.nkind.N_CALL) {
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
let callee: *syntax.node = e.lhs;
|
||||
if (callee == nil) { return nil; };
|
||||
// #31: synthesize the `alloc(value)` / `alloc([], n)` builtin
|
||||
@@ -3809,7 +4019,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// The TY_ERR stamp on the callee is cgen's routing key
|
||||
// (cstage spells it `n->lhs->type = ty_err`).
|
||||
if (syntax.streq(callee.str, "abort")
|
||||
&& syntax.scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
|
||||
&& lookupvisible(c, "abort") == nil) {
|
||||
if (e.list != nil) {
|
||||
let mt: *syntax.node = exprtype(c, e.list, nil);
|
||||
let conf: bool = false;
|
||||
@@ -3828,7 +4038,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
return tn;
|
||||
};
|
||||
if (syntax.streq(callee.str, "assert") && e.list != nil
|
||||
&& syntax.scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
|
||||
&& lookupvisible(c, "assert") == nil) {
|
||||
let ct: *syntax.node = exprtype(c, e.list, nil);
|
||||
if (ct != nil) {
|
||||
// No alias peel: cstage compares ty_bool by
|
||||
@@ -4010,11 +4220,11 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
if (nm.len > 0) {
|
||||
let s: *syntax.sym = nil;
|
||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||
s = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
||||
s = lookupvisible(c, nm);
|
||||
} else {
|
||||
let ms: *syntax.sym = nil;
|
||||
if (callee.lhs != nil && callee.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
ms = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
ms = lookupvisible(c, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
@@ -4036,7 +4246,17 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// global-leaf path) and harec check_autodereference
|
||||
// (ref/harec/src/check.c:1566-1581).
|
||||
if (ms != nil && (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32)) {
|
||||
s = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), nm);
|
||||
let mk: str = modkeyfor(c, callee.lhs.str);
|
||||
if (mk.len == 0) {
|
||||
if (ms.skind == syntax.skind.SK_USE) {
|
||||
packageaccesserr(c, callee, callee.lhs.str, nm, false);
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
} else {
|
||||
s = syntax.scopelookupinmodule(c.cur, mk, nm);
|
||||
};
|
||||
// Module-qualified callee whose leaf isn't scope-keyed
|
||||
// under its module: align to cstage, which stamps ty_err
|
||||
// here and lets cgen emit the call (cmd/wcc/check.c:1834-
|
||||
@@ -4050,6 +4270,10 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// keys the CALL off run's `//ww:module test` directive
|
||||
// either way, so the resolution change is asm-neutral.
|
||||
if (s == nil) {
|
||||
if (c.sepmode != 0 && ms.skind == syntax.skind.SK_USE && mk.len != 0
|
||||
&& !synthesizedtestrun(c, callee)) {
|
||||
packageaccesserr(c, callee, callee.lhs.str, nm, true);
|
||||
};
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
callee.type_ = c.tc.tyerr: *void; // N_DOT node itself (asserttyped checks it)
|
||||
return nil;
|
||||
@@ -4106,6 +4330,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
return nil;
|
||||
};
|
||||
if (k == syntax.nkind.N_DOT) {
|
||||
if (e.type_ == c.tc.tyerr: *void) { return nil; };
|
||||
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
||||
@@ -4120,7 +4345,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// ref/harec/src/check.c:4419-4434.
|
||||
let lhsn: *syntax.node = e.lhs;
|
||||
if (lhsn != nil) { if (lhsn.kind == syntax.nkind.N_IDENT) {
|
||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
||||
let ms: *syntax.sym = lookupvisible(c, lhsn.str);
|
||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, lhsn.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
@@ -4133,7 +4358,16 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// qualified resolution and the lenient checker policy
|
||||
// keeps the silent miss documented at scruttype L656.
|
||||
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, lhsn.str), e.str);
|
||||
let mk: str = modkeyfor(c, lhsn.str);
|
||||
if (mk.len == 0 && ms.skind == syntax.skind.SK_USE) {
|
||||
packageaccesserr(c, e, lhsn.str, e.str, false);
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
let fs: *syntax.sym = nil;
|
||||
if (mk.len != 0) {
|
||||
fs = syntax.scopelookupinmodule(c.cur, mk, e.str);
|
||||
};
|
||||
if (fs != nil) { if (fs.decl != nil) {
|
||||
// #34: a module-qualified bare fn rvalue `mod.fn` types as
|
||||
// its FN TYPE (twin of the N_IDENT arm, :2688); decl.lhs is
|
||||
@@ -4165,6 +4399,12 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
return tn;
|
||||
};
|
||||
}; };
|
||||
if (c.sepmode != 0 && fs == nil && ms.skind == syntax.skind.SK_USE && mk.len != 0
|
||||
&& !synthesizedtestrun(c, e)) {
|
||||
packageaccesserr(c, e, lhsn.str, e.str, true);
|
||||
e.type_ = c.tc.tyerr: *void;
|
||||
return nil;
|
||||
};
|
||||
};
|
||||
// Fold case 2 inner: bare `EnumT.MEMBER` where EnumT
|
||||
// is an SK_TYPE in the flat scope. Mirror cstage
|
||||
@@ -4310,7 +4550,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
// style anonymous struct lit we don't yet parse — bail.
|
||||
if (e.lhs == nil) { return nil; };
|
||||
if (e.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.lhs.str);
|
||||
let ms: *syntax.sym = lookupvisible(c, e.lhs.str);
|
||||
if (ms != nil) { if (ms.skind == syntax.skind.SK_TYPE) { if (ms.decl != nil) {
|
||||
let tn: *syntax.node = ms.decl.lhs;
|
||||
if (tn != nil) {
|
||||
@@ -4906,7 +5146,7 @@ fn assignableaddrfn(c: *checker, dst: *syntax.node, rhs: *syntax.node) bool = {
|
||||
// silently admitting a *fn into a foreign-sig slot or rejecting a
|
||||
// valid same-module &fn. cstage uses scope_lookup_prefer with
|
||||
// cur_mod (cmd/wcc/check.c:410, assignable_addrfn).
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, id.str);
|
||||
let s: *syntax.sym = lookupvisible(c, id.str);
|
||||
if (s == nil) { return false; };
|
||||
if (s.skind != syntax.skind.SK_FN) { return false; };
|
||||
if (s.decl == nil) { return false; };
|
||||
@@ -6043,7 +6283,7 @@ fn desugararrayslice(c: *checker, dsttn: *syntax.node, srctn: *syntax.node, val:
|
||||
fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
|
||||
if (callee == nil) { return nil; };
|
||||
if (callee.kind == syntax.nkind.N_IDENT) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.str);
|
||||
let s: *syntax.sym = lookupvisible(c, callee.str);
|
||||
if (s != nil) {
|
||||
if (s.skind == syntax.skind.SK_FN) { return s.decl; };
|
||||
};
|
||||
@@ -6052,14 +6292,18 @@ fn calleefndecl(c: *checker, callee: *syntax.node) *syntax.node = {
|
||||
if (callee.kind == syntax.nkind.N_DOT) {
|
||||
if (callee.lhs != nil) {
|
||||
if (callee.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
let ms: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
||||
let ms: *syntax.sym = lookupvisible(c, callee.lhs.str);
|
||||
if (ms != nil && ms.skind != syntax.skind.SK_USE) {
|
||||
let mu: *syntax.sym = syntax.scopelookupuselocal(ms.scope, callee.lhs.str);
|
||||
if (mu != nil) { ms = mu; };
|
||||
};
|
||||
if (ms != nil) {
|
||||
if (ms.skind == syntax.skind.SK_USE || ms.use_alias != 0i32) {
|
||||
let fs: *syntax.sym = syntax.scopelookupinmodule(c.cur, modkeyfor(c, callee.lhs.str), callee.str);
|
||||
let fs: *syntax.sym = nil;
|
||||
let mk: str = modkeyfor(c, callee.lhs.str);
|
||||
if (mk.len != 0) {
|
||||
fs = syntax.scopelookupinmodule(c.cur, mk, callee.str);
|
||||
};
|
||||
if (fs != nil) {
|
||||
if (fs.skind == syntax.skind.SK_FN) { return fs.decl; };
|
||||
};
|
||||
@@ -6218,7 +6462,7 @@ fn checkassign(c: *checker, n: *syntax.node) void = {
|
||||
// cmd/wcc/check.c:1889-1896.
|
||||
if (n.lhs.kind == syntax.nkind.N_IDENT) {
|
||||
if (n.lhs.str.len > 0) {
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, n.lhs.str);
|
||||
let s: *syntax.sym = lookupvisible(c, n.lhs.str);
|
||||
if (s != nil) {
|
||||
if (s.is_const != 0i32) {
|
||||
cerr("error: cannot assign to const binding\n");
|
||||
@@ -6763,7 +7007,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// either spuriously rejecting valid `?` code or skipping the F8
|
||||
// reject. Mirrors exprtype's own N_IDENT arm (scopelookupprefer
|
||||
// at :2897); cstage types the operand via cexpr with cur_mod.
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, e.str);
|
||||
let s: *syntax.sym = lookupvisible(c, e.str);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
return s.decl.lhs;
|
||||
@@ -6791,7 +7035,7 @@ fn exprtypeoftry(c: *checker, e: *syntax.node) *syntax.node = {
|
||||
// N_CALL arm (scopelookupprefer at :3336). The N_DOT-callee leaf
|
||||
// (callee.str above) still resolves bare-leaf, not via the module
|
||||
// qualifier callee.lhs.str — that module-keyed fix rides task #51.
|
||||
let s: *syntax.sym = syntax.scopelookupprefer(c.cur, c.curmod, nm);
|
||||
let s: *syntax.sym = lookupvisible(c, nm);
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != syntax.skind.SK_FN) { return nil; };
|
||||
if (s.decl == nil) { return nil; };
|
||||
@@ -7028,7 +7272,7 @@ fn isassertfam(c: *checker, id: *syntax.node) bool = {
|
||||
if (!syntax.streq(id.str, "abort") && !syntax.streq(id.str, "assert")) {
|
||||
return false;
|
||||
};
|
||||
return syntax.scopelookupprefer(c.cur, c.curmod, id.str) == nil;
|
||||
return lookupvisible(c, id.str) == nil;
|
||||
};
|
||||
|
||||
// asserttyped — post-checker invariant gate (#15, A.6.2.1e). Walks the
|
||||
@@ -7089,7 +7333,10 @@ fn asserttyped(c: *checker, n: *syntax.node, indot: bool) void = {
|
||||
if (isexpr && k == syntax.nkind.N_IDENT) {
|
||||
if (indot) { skip = true; };
|
||||
if (!skip) {
|
||||
let s: *syntax.sym = syntax.scopelookup(c.cur, n.str);
|
||||
// Use the same source-visibility rules as expression
|
||||
// resolution. A transitive same-leaf declaration must not hide
|
||||
// a decl-less pseudo-builtin from this invariant gate.
|
||||
let s: *syntax.sym = lookupvisible(c, n.str);
|
||||
if (s != nil) {
|
||||
if (s.skind == syntax.skind.SK_USE) { skip = true; };
|
||||
if (s.decl == nil) { skip = true; };
|
||||
@@ -7146,6 +7393,8 @@ export fn checkinit(c: *checker, tc: *syntax.tctx) void = {
|
||||
c.nunresolved = 0;
|
||||
c.errs = 0;
|
||||
c.istest = 0i32; // #15: caller (w6c main) sets it after init
|
||||
c.sepmode = 0i32; // caller (w6c main) sets it from -c
|
||||
c.synthtestrun = nil;
|
||||
c.verbose = 0;
|
||||
c.fnret = nil;
|
||||
let empty: str;
|
||||
@@ -7395,6 +7644,7 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
||||
did.str = "test";
|
||||
dot.lhs = did;
|
||||
dot.str = "run";
|
||||
c.synthtestrun = dot;
|
||||
call.lhs = dot;
|
||||
call.list = arg;
|
||||
let ret: *syntax.node = syntax.newnode(syntax.nkind.N_RETURN, pf, pl, pc);
|
||||
|
||||
@@ -668,10 +668,14 @@ export fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = {
|
||||
};
|
||||
wwisortdecls(upaths, unodes, nuse);
|
||||
let i: i32 = 0;
|
||||
let previous: str = "";
|
||||
for (i < nuse) {
|
||||
wputs(fd, "import ");
|
||||
wputs(fd, upaths[i]);
|
||||
wputs(fd, ";\n");
|
||||
if (previous.len == 0 || !syntax.streq(previous, upaths[i])) {
|
||||
wputs(fd, "import ");
|
||||
wputs(fd, upaths[i]);
|
||||
wputs(fd, ";\n");
|
||||
previous = upaths[i];
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -77,6 +77,29 @@ must_contain(const char *src, const char *needle)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *
|
||||
imports_to_str(const char *src, int *errs, const char **pkg)
|
||||
{
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
lexinit(&l, a, "imports.ww", src, strlen(src));
|
||||
parserinit(&p, a, &l);
|
||||
Node *n = parseimports(&p);
|
||||
*errs = p.errs + l.errs;
|
||||
*pkg = n->module ? strdup(n->module) : NULL;
|
||||
|
||||
char *buf = NULL;
|
||||
size_t len = 0;
|
||||
FILE *f = open_memstream(&buf, &len);
|
||||
if (f != NULL) {
|
||||
astprint(f, n);
|
||||
fclose(f);
|
||||
}
|
||||
freearena(a);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
@@ -179,6 +202,68 @@ main(void)
|
||||
if (!must_contain("fn f() i32 = { return a + b; };", "(bin +")) fail++;
|
||||
if (!must_contain("@symbol(\"x\") fn f() void;", "(attr \"symbol\""))fail++;
|
||||
|
||||
{
|
||||
const char *src =
|
||||
"package main;\n"
|
||||
"import zed;\n"
|
||||
"fn f() void = { let s: str = \"import fake;\"; };\n"
|
||||
"/* import hidden; */\n"
|
||||
"import alpha;\n";
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = imports_to_str(src, &errs, &pkg);
|
||||
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||
|| got == NULL || strstr(got, "(use \"zed\"") == NULL
|
||||
|| strstr(got, "(use \"alpha\"") == NULL
|
||||
|| strstr(got, "fake") != NULL || strstr(got, "hidden") != NULL) {
|
||||
fprintf(stderr, "imports-only parse mismatch:\n%s\n",
|
||||
got ? got : "<null>");
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = imports_to_str("package main;\nimport ;\n", &errs,
|
||||
&pkg);
|
||||
if (errs == 0) {
|
||||
fputs("malformed import accepted\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = imports_to_str(
|
||||
"package main;\n@trace import alpha;\n", &errs, &pkg);
|
||||
if (errs == 0 || got == NULL
|
||||
|| strstr(got, "(use \"alpha\"") == NULL) {
|
||||
fputs("attributed import was not rejected and retained\n",
|
||||
stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
{
|
||||
int errs;
|
||||
const char *pkg;
|
||||
char *got = imports_to_str(
|
||||
"//ww:module example.main\n"
|
||||
"package main;\nimport alpha;\n", &errs, &pkg);
|
||||
if (errs != 0 || pkg == NULL || strcmp(pkg, "main") != 0
|
||||
|| got == NULL || strstr(got, "(use \"alpha\"") == NULL) {
|
||||
fputs("imports-only parse rejected module boundary\n", stderr);
|
||||
fail++;
|
||||
}
|
||||
free((void *)pkg);
|
||||
free(got);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d parse tests failed\n", fail);
|
||||
return 1;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
//ww:error c "is not a variant of" ww "ambiguous without nominal layout"
|
||||
// migrated from test/wcc/787_xmod_variant_match.c: a same-leaf variant from a DIFFERENT module is NOT a variant of the scrutinee — guards #13 against bare leaf-strip false-accept.
|
||||
//ww:error c "is not a variant of" ww "not a variant of scrutinee"
|
||||
// migrated from test/wcc/787_xmod_variant_match.c: a same-leaf variant from a
|
||||
// DIFFERENT module is NOT a variant of the scrutinee — guards #13 against bare
|
||||
// leaf-strip false-accept. Both checkers now resolve the direct qualifier before
|
||||
// codegen and reject at the case boundary.
|
||||
package pkg;
|
||||
export type a = !void;
|
||||
export type b = !void;
|
||||
|
||||
Reference in New Issue
Block a user