ww: separate package identity from declared name
This commit is contained in:
198
cmd/wcc/check.c
198
cmd/wcc/check.c
@@ -58,8 +58,10 @@ 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 const char *use_path(Node *file, const char *curmod, int source,
|
||||
const char *alias);
|
||||
static int src_imports(Node *file, const char *modtag, int source,
|
||||
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);
|
||||
@@ -89,6 +91,7 @@ resolve_typename(Checker *c, Node *n)
|
||||
/* M1 #22: map the qualifier alias to its dotted
|
||||
* import path (symbols are path-keyed). */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
c->cur_source,
|
||||
head);
|
||||
if (mk != NULL)
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
@@ -622,7 +625,8 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
case N_DOT: {
|
||||
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);
|
||||
const char *mk = use_path(c->file, c->cur_mod, c->cur_source,
|
||||
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 ||
|
||||
@@ -636,12 +640,15 @@ eval_def_const(Checker *c, Node *n, u64 *out, int depth)
|
||||
}
|
||||
|
||||
int
|
||||
check_eval_const(Checker *c, Node *n, const char *owner, u64 *out)
|
||||
check_eval_const(Checker *c, Node *n, const char *owner, int source, u64 *out)
|
||||
{
|
||||
const char *saved = c->cur_mod;
|
||||
int savesource = c->cur_source;
|
||||
c->cur_mod = owner;
|
||||
c->cur_source = source;
|
||||
int ok = eval_def_const(c, n, out, 0);
|
||||
c->cur_mod = saved;
|
||||
c->cur_source = savesource;
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -1475,6 +1482,7 @@ cexpr(Checker *c, Node *n)
|
||||
* import path; map the alias the user wrote to
|
||||
* that path before looking up the leaf. */
|
||||
const char *mk = use_path(c->file, c->cur_mod,
|
||||
c->cur_source,
|
||||
n->lhs->str);
|
||||
if (mk == NULL) {
|
||||
if (ms->kind == SK_USE)
|
||||
@@ -2854,6 +2862,7 @@ check_init(Checker *c, Arena *a)
|
||||
c->is_test = 0; /* #15: caller (w6c main) sets it after init */
|
||||
c->is_test_package = 0;
|
||||
c->test_module = "test";
|
||||
c->test_target = NULL;
|
||||
typesinit(a);
|
||||
c->top = newscope(a, NULL);
|
||||
c->cur = c->top;
|
||||
@@ -2873,6 +2882,10 @@ static const char *
|
||||
decl_mod(Node *file, Node *d)
|
||||
{
|
||||
if (d == NULL || d->module == NULL || file == NULL) return NULL;
|
||||
/* Separate-compilation interfaces already carry their canonical owner.
|
||||
* Visibility is checked independently against the referencing source's
|
||||
* import binding; never erase semantic ownership for a transitive fact. */
|
||||
if (d->imported) return d->module;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
/* M1 #22: a decl is imported iff some `use` directive's full
|
||||
* dotted import path equals the decl's module (now the path,
|
||||
@@ -2887,41 +2900,40 @@ decl_mod(Node *file, Node *d)
|
||||
}
|
||||
|
||||
/*
|
||||
* use_path — map a `use` alias (leaf bareword the user writes, `utf8`)
|
||||
* to the full dotted import path it binds (`encoding.utf8`), for the
|
||||
* module-qualified resolution and the codegen hint (M1 #22, §2.4). For
|
||||
* single-level packages usepath == alias so the result is unchanged.
|
||||
* use_path — map a source-file default qualifier (the imported package's
|
||||
* declared name) to the full canonical import path it binds, for
|
||||
* module-qualified resolution and the codegen hint (M1 #22, §2.4).
|
||||
*
|
||||
* The alias→path map is NOT file-global: two modules in the same
|
||||
* 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; requiring it closes both
|
||||
* cross-module mis-resolution and accidental transitive-import visibility.
|
||||
* The qualifier→path map is source-file local: two files in the same
|
||||
* concatenated unit may bind the same declared name to different paths. The
|
||||
* import with the reference's source ID and owner is authoritative, closing
|
||||
* both cross-file mis-resolution and accidental transitive visibility.
|
||||
* Returns NULL if the referencing package has no such `use`.
|
||||
*/
|
||||
static const char *
|
||||
use_path(Node *file, const char *curmod, const char *alias)
|
||||
use_path(Node *file, const char *curmod, int source, const char *alias)
|
||||
{
|
||||
if (file == NULL || alias == NULL) return 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) {
|
||||
if (source == 0 && 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)
|
||||
|| u->sourceid != source || strcmp(u->str, alias) != 0)
|
||||
continue;
|
||||
const char *p = u->usepath ? u->usepath : u->str;
|
||||
const char *um = decl_mod(file, u);
|
||||
int same = (um == NULL) ? (curmod == NULL)
|
||||
: (curmod != NULL && strcmp(um, curmod) == 0);
|
||||
if (same)
|
||||
if (same) {
|
||||
u->used = 1;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -2947,9 +2959,12 @@ resolve_typedecl(Checker *c, Node *d)
|
||||
if (t == NULL || t->under != NULL || t->resolving) return;
|
||||
t->resolving = 1;
|
||||
const char *save = c->cur_mod;
|
||||
int savesource = c->cur_source;
|
||||
c->cur_mod = decl_mod(c->file, d);
|
||||
c->cur_source = d->sourceid;
|
||||
Type *under = resolve_type(c, d->lhs);
|
||||
c->cur_mod = save;
|
||||
c->cur_source = savesource;
|
||||
/* Alias-root cycle (`type a = b; type b = a` / `type a = a`):
|
||||
* checked BEFORE clearing the flag so self-aliases trip on their
|
||||
* own in-progress mark. ty_err instead of the cyclic under keeps
|
||||
@@ -2978,11 +2993,11 @@ resolve_typedecl(Checker *c, Node *d)
|
||||
* (N_USE nodes with module == NULL).
|
||||
*/
|
||||
static int
|
||||
src_imports(Node *file, const char *modtag, const char *name)
|
||||
src_imports(Node *file, const char *modtag, int source, const char *name)
|
||||
{
|
||||
if (file == NULL || name == NULL || name[0] == '\0') return 0;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE) continue;
|
||||
if (u->kind != N_USE || u->sourceid != source) continue;
|
||||
/* Skip self-imports: lib/fmt/fmt_test.ww carries `use fmt;`
|
||||
* even though its module tag is also "fmt"; that directive
|
||||
* doesn't introduce a foreign module bareword and lib/fmt's
|
||||
@@ -3013,10 +3028,18 @@ 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;
|
||||
for (Node *u = c->file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->sourceid != c->cur_source) continue;
|
||||
const char *um = decl_mod(c->file, u);
|
||||
int same = c->cur_mod == NULL ? um == NULL
|
||||
: um != NULL && strcmp(um, c->cur_mod) == 0;
|
||||
const char *path = u->usepath ? u->usepath : u->str;
|
||||
if (same && path != NULL && strcmp(path, mod) == 0) {
|
||||
u->used = 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Sym *
|
||||
@@ -3035,7 +3058,7 @@ lookup_visible(Checker *c, const char *name)
|
||||
* 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) {
|
||||
if (use_path(c->file, c->cur_mod, c->cur_source, 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
|
||||
@@ -3101,7 +3124,7 @@ check_module_shadow(Checker *c, const char *name, Pos pos,
|
||||
}
|
||||
}
|
||||
if (!seen_use) return;
|
||||
if (!src_imports(c->file, c->cur_mod, name)) return;
|
||||
if (!src_imports(c->file, c->cur_mod, c->cur_source, name)) return;
|
||||
err(c, pos, "%s '%s' shadows imported module '%s'",
|
||||
kindstr, name, name);
|
||||
}
|
||||
@@ -3132,6 +3155,80 @@ same_import_fact(Checker *c, Node *d, const char *mod, Skind kind)
|
||||
return s;
|
||||
}
|
||||
|
||||
static int
|
||||
top_decl_kind(Node *d)
|
||||
{
|
||||
return d != NULL && (d->kind == N_TYPEDECL || d->kind == N_DEF
|
||||
|| d->kind == N_FNDECL || d->kind == N_LET);
|
||||
}
|
||||
|
||||
static void
|
||||
check_import_alt(Node *d, const char *name)
|
||||
{
|
||||
FILE *f = errout ? errout : stderr;
|
||||
fprintf(f, "\t%s:%d:%d: other declaration of %s\n",
|
||||
d->pos.file ? d->pos.file : "?", d->pos.line, d->pos.col, name);
|
||||
}
|
||||
|
||||
/* Go's default import binding lives in the importing file's scope. Reject
|
||||
* only another binding in that same source section; equal names in sibling
|
||||
* files are independent even though their canonical edges are package-wide. */
|
||||
static void
|
||||
check_import_redeclarations(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->str == NULL)
|
||||
continue;
|
||||
for (Node *v = file->list; v != u; v = v->next) {
|
||||
if (v->kind != N_USE || v->imported || v->str == NULL
|
||||
|| v->sourceid != u->sourceid)
|
||||
continue;
|
||||
if (strcmp(v->str, u->str) == 0) {
|
||||
err(c, u->pos, "%s redeclared in this block", u->str);
|
||||
check_import_alt(v, u->str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Report file-local unused bindings before package-declaration collisions,
|
||||
* matching the pinned Go resolver's stable ordering. The package declarations
|
||||
* themselves are package-scoped, so they collide with an equal import name in
|
||||
* any contributing source file. */
|
||||
static void
|
||||
check_import_usage_and_collisions(Checker *c, Node *file)
|
||||
{
|
||||
if (!c->sep_mode) return;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->used || u->str == NULL)
|
||||
continue;
|
||||
const char *path = u->usepath ? u->usepath : u->str;
|
||||
const char *dot = strrchr(path, '.');
|
||||
const char *leaf = dot ? dot + 1 : path;
|
||||
if (strcmp(u->str, leaf) == 0)
|
||||
err(c, u->pos, "\"%s\" imported and not used", path);
|
||||
else
|
||||
err(c, u->pos, "\"%s\" imported as %s and not used",
|
||||
path, u->str);
|
||||
}
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->imported || !top_decl_kind(d) || d->str == NULL)
|
||||
continue;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind != N_USE || u->imported || u->str == NULL
|
||||
|| strcmp(d->str, u->str) != 0)
|
||||
continue;
|
||||
const char *path = u->usepath ? u->usepath : u->str;
|
||||
err(c, d->pos,
|
||||
"%s already declared through import of package %s (\"%s\")",
|
||||
d->str, u->str, path);
|
||||
check_import_alt(u, d->str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
check_file(Checker *c, Node *file)
|
||||
{
|
||||
@@ -3148,21 +3245,30 @@ check_file(Checker *c, Node *file)
|
||||
* already emits the qualified call. wwstage twin in check.ww. */
|
||||
if (c->is_test) {
|
||||
int present = 0;
|
||||
for (Node *u = file->list; u; u = u->next)
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (c->test_target != NULL && u->kind == N_USE
|
||||
&& !u->imported && u->usepath
|
||||
&& (strcmp(u->usepath, c->test_target) == 0
|
||||
|| strcmp(u->usepath, c->test_module) == 0))
|
||||
u->used = 1;
|
||||
if (u->kind == N_USE && !u->imported
|
||||
&& u->usepath && strcmp(u->usepath, c->test_module) == 0) {
|
||||
present = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!present) {
|
||||
Node *usenode = newnode(c->a, N_USE, file->pos);
|
||||
usenode->str = c->test_module;
|
||||
usenode->strlen = strlen(c->test_module);
|
||||
usenode->usepath = c->test_module;
|
||||
usenode->pkgname = file->pkgname;
|
||||
usenode->sourceid = file->sourceid;
|
||||
if (c->test_target != NULL) usenode->used = 1;
|
||||
usenode->next = file->list;
|
||||
file->list = usenode;
|
||||
}
|
||||
}
|
||||
check_import_redeclarations(c, file);
|
||||
|
||||
/* pass 1: install names (types first, then defs/fns).
|
||||
* For self-referential types we install the named-type placeholder
|
||||
@@ -3172,13 +3278,10 @@ check_file(Checker *c, Node *file)
|
||||
* references (`strconv.invalid`) resolve when typedecl bodies are
|
||||
* walked in the next pass. */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_source = d->sourceid;
|
||||
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). */
|
||||
/* A package may not import its own canonical owner. Import
|
||||
* usage and membership are checked with source-file provenance. */
|
||||
const char *owner = decl_mod(file, d);
|
||||
/* M1 #22: self-import ⟺ the imported path equals the
|
||||
* use's own (owning) module path. Compares paths, not
|
||||
@@ -3233,9 +3336,11 @@ check_file(Checker *c, Node *file)
|
||||
* here. A foldable stub carries type NULL until then. A duplicate
|
||||
* (prev already bound non-USE) is left for that loop to diagnose. */
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_DEF) continue;
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
c->cur_source = d->sourceid;
|
||||
const char *mod = decl_mod(file, d);
|
||||
if (same_import_fact(c, d, mod, SK_DEF) != NULL)
|
||||
continue;
|
||||
@@ -3250,13 +3355,16 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_TYPEDECL) continue;
|
||||
resolve_typedecl(c, d);
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
c->cur_source = d->sourceid;
|
||||
switch (d->kind) {
|
||||
case N_USE:
|
||||
/* already installed in pass 1; no-op here so the
|
||||
@@ -3360,6 +3468,7 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
|
||||
/* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry
|
||||
* is the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
@@ -3487,8 +3596,15 @@ check_file(Checker *c, Node *file)
|
||||
nm->strlen = strlen(d->str);
|
||||
Node *id;
|
||||
if (d->imported && d->module && d->module[0]) {
|
||||
const char *dotp = strrchr(d->module, '.');
|
||||
const char *alias = dotp ? dotp + 1 : d->module;
|
||||
const char *alias = d->pkgname && d->pkgname[0]
|
||||
? d->pkgname : d->module;
|
||||
/* A generated dispatcher cannot bind a command test
|
||||
* package as `main`: that would collide with its own entry.
|
||||
* This is a compiler-owned binding, never source alias syntax;
|
||||
* use the canonical target path as its private qualifier. */
|
||||
if (c->test_target != NULL
|
||||
&& strcmp(d->module, c->test_target) == 0)
|
||||
alias = c->test_target;
|
||||
id = newnode(c->a, N_DOT, fp);
|
||||
id->lhs = newnode(c->a, N_IDENT, fp);
|
||||
id->lhs->str = alias;
|
||||
@@ -3542,6 +3658,8 @@ check_file(Checker *c, Node *file)
|
||||
tab = newnode(c->a, N_LET, fp);
|
||||
tab->op = TK_CONST;
|
||||
tab->str = "__wwtests";
|
||||
tab->pkgname = file->pkgname;
|
||||
tab->sourceid = file->sourceid;
|
||||
tab->lhs = tsl;
|
||||
tab->rhs = arr;
|
||||
/* pass 1 already ran, so install the table's name now —
|
||||
@@ -3581,10 +3699,15 @@ check_file(Checker *c, Node *file)
|
||||
Node *m = newnode(c->a, N_FNDECL, fp);
|
||||
m->str = "main";
|
||||
m->export = 1;
|
||||
m->pkgname = file->pkgname;
|
||||
m->sourceid = file->sourceid;
|
||||
m->lhs = newnode(c->a, N_TNAME, fp);
|
||||
m->lhs->str = "i32";
|
||||
m->body = body;
|
||||
int savesource = c->cur_source;
|
||||
c->cur_source = m->sourceid;
|
||||
m->type = build_fn_type(c, m);
|
||||
c->cur_source = savesource;
|
||||
/* pass 1 already ran, so the install loop never stamped m's
|
||||
* type; set it explicitly (pass 2 below reads d->type).
|
||||
* Append the table const (if any) then main to file->list. */
|
||||
@@ -3603,6 +3726,7 @@ check_file(Checker *c, Node *file)
|
||||
/* pass 2: check def initialisers and fn bodies */
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
c->cur_mod = decl_mod(file, d);
|
||||
c->cur_source = d->sourceid;
|
||||
switch (d->kind) {
|
||||
case N_DEF: {
|
||||
if (d->rhs) {
|
||||
@@ -3786,6 +3910,8 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
c->cur_source = 0;
|
||||
check_import_usage_and_collisions(c, file);
|
||||
|
||||
/*
|
||||
* #6 harec-fidelity (ref/harec/src/check.c:3941): a @test fn is
|
||||
|
||||
@@ -1327,9 +1327,9 @@ parseuse(Parser *p)
|
||||
Pos pp = p->cur.pos;
|
||||
expect(p, TK_USE);
|
||||
Node *n = newnode(p->a, N_USE, pp);
|
||||
/* 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`). */
|
||||
/* M1 #22: accumulate the full dotted import path in usepath. `str`
|
||||
* starts as its leaf; direct export metadata later installs the imported
|
||||
* declaration's default name without changing canonical identity. */
|
||||
const char *leaf = expectident(p);
|
||||
const char *path = leaf;
|
||||
while (accept(p, TK_DOT)) {
|
||||
@@ -1358,19 +1358,23 @@ parseimports(Parser *p)
|
||||
* the following package clause and imports. */
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
sawpackage = 0;
|
||||
p->sourceid++;
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = NULL;
|
||||
p->curpkg = NULL;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
const char *rp = p->cur.text;
|
||||
sawpackage = 0;
|
||||
p->sourceid++;
|
||||
advance(p);
|
||||
p->pathmod = NULL;
|
||||
p->curmod = rp;
|
||||
p->resetmod = rp;
|
||||
p->curpkg = NULL;
|
||||
continue;
|
||||
}
|
||||
if (p->cur.kind == TK_MODULE) {
|
||||
@@ -1385,9 +1389,13 @@ parseimports(Parser *p)
|
||||
}
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curmod = name;
|
||||
p->curpkg = name;
|
||||
if (p->pathmod == NULL && p->resetmod == NULL)
|
||||
p->curmod = name;
|
||||
Node *package = newnode(p->a, N_FILE, pp);
|
||||
package->module = name;
|
||||
package->module = p->curmod;
|
||||
package->pkgname = name;
|
||||
package->sourceid = p->sourceid;
|
||||
if (packages == NULL)
|
||||
packages = package;
|
||||
else
|
||||
@@ -1395,6 +1403,8 @@ parseimports(Parser *p)
|
||||
packagetail = package;
|
||||
if (!sawpackage) {
|
||||
file->module = name;
|
||||
file->pkgname = name;
|
||||
file->sourceid = p->sourceid;
|
||||
file->pos = pp;
|
||||
sawpackage = 1;
|
||||
}
|
||||
@@ -1408,6 +1418,8 @@ parseimports(Parser *p)
|
||||
if (p->cur.kind == TK_USE) {
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
d->pkgname = p->curpkg;
|
||||
d->sourceid = p->sourceid;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
@@ -1424,6 +1436,8 @@ parseimports(Parser *p)
|
||||
p->errs++;
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
d->pkgname = p->curpkg;
|
||||
d->sourceid = p->sourceid;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
@@ -1441,6 +1455,8 @@ parseimports(Parser *p)
|
||||
p->errs++;
|
||||
Node *d = parseuse(p);
|
||||
d->module = p->curmod;
|
||||
d->pkgname = p->curpkg;
|
||||
d->sourceid = p->sourceid;
|
||||
if (head == NULL)
|
||||
head = d;
|
||||
else
|
||||
@@ -1546,6 +1562,7 @@ parsefile(Parser *p)
|
||||
Pos pp = { p->l->file, 1, 1 };
|
||||
Node *file = newnode(p->a, N_FILE, pp);
|
||||
Node *head = NULL, *tail = NULL;
|
||||
Node *packages = NULL, *packagetail = NULL;
|
||||
int sawpackage = 0;
|
||||
while (p->cur.kind != TK_EOF) {
|
||||
/* `package foo;` — directory-as-module declaration. Every
|
||||
@@ -1555,58 +1572,41 @@ parsefile(Parser *p)
|
||||
* sep primary-reset (resetmod) regions carry identity
|
||||
* out-of-band and are exempt. */
|
||||
if (p->cur.kind == TK_MODULE) {
|
||||
Pos packagepos = p->cur.pos;
|
||||
sawpackage = 1;
|
||||
advance(p);
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
if (p->pathmod != NULL || p->resetmod != NULL) {
|
||||
/* M1 #22: while an import path is active the
|
||||
* in-file `package` clause is an ASSERTION — its
|
||||
* leaf must equal the path's last component; it
|
||||
* does NOT overwrite the path-derived module.
|
||||
* #57 extends this to the sep primary-reset path
|
||||
* (resetmod): the dotted reset path is the
|
||||
* authoritative identity, the clause asserts. */
|
||||
const char *active =
|
||||
p->pathmod ? p->pathmod : p->resetmod;
|
||||
const char *dot = strrchr(active, '.');
|
||||
const char *last = dot ? dot + 1 : active;
|
||||
int testsupport = p->testmodule != NULL
|
||||
&& strcmp(p->testmodule, "__wwtest") == 0
|
||||
&& strcmp(active, "__wwtest") == 0
|
||||
&& strcmp(name, "test") == 0;
|
||||
int commandpackage = p->commandpackage
|
||||
&& p->pathmod == NULL && p->resetmod != NULL
|
||||
&& (strcmp(name, "main") == 0
|
||||
|| strcmp(name, "main_test") == 0);
|
||||
if (strcmp(name, last) != 0 && !testsupport
|
||||
&& !commandpackage) {
|
||||
errorf(p->cur.pos,
|
||||
"package %s does not match import path %s",
|
||||
name, active);
|
||||
p->errs++;
|
||||
}
|
||||
} else {
|
||||
p->curpkg = name;
|
||||
if (p->pathmod == NULL && p->resetmod == NULL) {
|
||||
p->curmod = name;
|
||||
/* #11: stamp the primary module identity on the
|
||||
* N_FILE node so wwi_emit can derive the
|
||||
* `package` leaf even when the body carries zero
|
||||
* module-tagged decls. Primary identity only;
|
||||
* never the imported boundary (TK_MODPATH). */
|
||||
if (file->module == NULL)
|
||||
file->module = name;
|
||||
}
|
||||
Node *package = newnode(p->a, N_FILE, packagepos);
|
||||
package->module = p->curmod;
|
||||
package->pkgname = name;
|
||||
package->sourceid = p->sourceid;
|
||||
package->imported = p->pathmod != NULL;
|
||||
if (packages == NULL) packages = package;
|
||||
else packagetail->next = package;
|
||||
packagetail = package;
|
||||
if (file->pkgname == NULL) {
|
||||
file->pkgname = name;
|
||||
file->sourceid = p->sourceid;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* `//ww:module <path>` — M1 #22 import boundary. The following
|
||||
* file's decls mangle on the full dotted import path, not the
|
||||
* leaf `package` clause, and are flagged imported (gates the
|
||||
* file's decls mangle on the full dotted import path independently
|
||||
* of its `package` clause, and are flagged imported (gates the
|
||||
* root-only bare-`main` rule, #32). */
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
sawpackage = 0;
|
||||
p->sourceid++;
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = NULL;
|
||||
p->curpkg = NULL;
|
||||
if (file->module == NULL) file->module = p->cur.text;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
@@ -1620,6 +1620,7 @@ parsefile(Parser *p)
|
||||
* decls to bare — that usage is deliberate-only. */
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
sawpackage = 0;
|
||||
p->sourceid++;
|
||||
/* #57: a path-carrying reset (sep primary body) mangles
|
||||
* decls on the dotted path so definer == importer, but
|
||||
* leaves imported==0 (curmod set, pathmod NULL) so -c
|
||||
@@ -1630,6 +1631,7 @@ parsefile(Parser *p)
|
||||
const char *rp = p->cur.text;
|
||||
advance(p);
|
||||
p->pathmod = NULL;
|
||||
p->curpkg = NULL;
|
||||
if (rp != NULL) {
|
||||
p->curmod = rp;
|
||||
p->resetmod = rp;
|
||||
@@ -1679,14 +1681,17 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
if (d != NULL) {
|
||||
d->module = p->curmod;
|
||||
d->imported = (p->pathmod != NULL);
|
||||
if (d != NULL) {
|
||||
d->module = p->curmod;
|
||||
d->pkgname = p->curpkg;
|
||||
d->sourceid = p->sourceid;
|
||||
d->imported = (p->pathmod != NULL);
|
||||
}
|
||||
if (head == NULL) head = d;
|
||||
else tail->next = d;
|
||||
tail = d;
|
||||
}
|
||||
file->list = head;
|
||||
file->body = packages;
|
||||
return file;
|
||||
}
|
||||
|
||||
36
cmd/wcc/ww.h
36
cmd/wcc/ww.h
@@ -173,7 +173,7 @@ typedef enum {
|
||||
* that replaces the withdrawn `package main` inject). */
|
||||
TK_MODPATH, /* `//ww:module <dotted-path>` — driver import boundary:
|
||||
* the following file's decls mangle on the full import
|
||||
* path, not the leaf `package` clause (M1 #22). Token
|
||||
* path independently of the `package` clause (M1 #22). Token
|
||||
* text carries the dotted path. */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
@@ -344,10 +344,16 @@ struct Node {
|
||||
* this is the importing (owning)
|
||||
* module. */
|
||||
const char *usepath; /* M1 #22: on an N_USE node, the full
|
||||
* dotted IMPORT path (`encoding.utf8`)
|
||||
* vs the leaf alias in `str`. Drives
|
||||
* canonical import path (`encoding.utf8`);
|
||||
* `str` is the declared default qualifier. Drives
|
||||
* the path-keyed decl_mod match and the
|
||||
* qualified-ref codegen hint. */
|
||||
* qualified-ref codegen hint. */
|
||||
const char *pkgname; /* declared package name for this source/export
|
||||
* section; independent of canonical `module`. */
|
||||
int sourceid; /* lexical source-file scope within the parsed
|
||||
* owner unit; module-reset/module boundaries
|
||||
* advance it deterministically. */
|
||||
int used; /* N_USE: checker observed this file-local binding. */
|
||||
int imported; /* M1 #22: decl reached through an
|
||||
* `//ww:module <path>` import boundary
|
||||
* (vs root/primary). Gates the root-only
|
||||
@@ -373,12 +379,12 @@ struct Parser {
|
||||
* import path; while set, decls stamp
|
||||
* module=pathmod and imported=1, and the
|
||||
* in-file `package` clause is an assertion. */
|
||||
const char *resetmod; /* #57: active `//ww:module-reset <path>` dotted
|
||||
* path; mangles decls on the path WITHOUT
|
||||
* imported=1 (primary-ness for -c and the #32
|
||||
* bare-main rule stay intact), and the in-file
|
||||
* `package` clause asserts (leaf == last
|
||||
* component) instead of overwriting curmod. */
|
||||
const char *resetmod; /* #57: active `//ww:module-reset <path>` canonical
|
||||
* identity; decls mangle on it without becoming
|
||||
* imported. The package clause independently
|
||||
* supplies the declared name. */
|
||||
const char *curpkg; /* declared name of the active source section. */
|
||||
int sourceid; /* deterministic lexical source-section ordinal. */
|
||||
const char *testmodule; /* hidden package-driver alias for toolchain
|
||||
* `package test`; NULL outside that compile */
|
||||
int commandpackage; /* selected command family: package main/main_test
|
||||
@@ -581,8 +587,10 @@ struct Checker {
|
||||
* currently being checked; NULL for primary
|
||||
* compilation unit. Drives same-module
|
||||
* preference in bare-leaf lookups so a bare
|
||||
* `read` inside lib/os resolves to os.read
|
||||
* rather than colliding io.read. */
|
||||
* `read` inside lib/os resolves to os.read
|
||||
* rather than colliding io.read. */
|
||||
int cur_source; /* lexical source-file scope of the declaration
|
||||
* currently being checked. */
|
||||
Node *file; /* current N_FILE root; used by check_module_shadow
|
||||
* to consult the declaring source file's own `use`
|
||||
* directives when refusing param/let names that
|
||||
@@ -596,6 +604,8 @@ struct Checker {
|
||||
* bodies and export compiler-private metadata,
|
||||
* but do not synthesize an entry. */
|
||||
const char *test_module; /* generated dispatcher support qualifier */
|
||||
const char *test_target; /* canonical target path used only as the
|
||||
* compiler-owned generated-main qualifier */
|
||||
int sep_mode; /* -c package compilation: imported interfaces are
|
||||
* present, so absent members are hard export errors. */
|
||||
Node *synth_test_run; /* exact compiler-generated support.run DOT;
|
||||
@@ -620,6 +630,6 @@ int fold_int_literal(Node*, u64*);
|
||||
/* Re-evaluate a checked integer constant under the declaration owner's
|
||||
* import scope. The compiler export writer uses this to canonicalize array
|
||||
* dimensions without serializing source-level constant dependencies. */
|
||||
int check_eval_const(Checker*, Node*, const char *owner, u64*);
|
||||
int check_eval_const(Checker*, Node*, const char *owner, int source, u64*);
|
||||
|
||||
#endif /* WW_H */
|
||||
|
||||
Reference in New Issue
Block a user