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
|
||||
|
||||
Reference in New Issue
Block a user