Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical. Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id).
This commit is contained in:
@@ -1167,6 +1167,43 @@ struct Mod {
|
||||
};
|
||||
static Mod *mod_map;
|
||||
|
||||
/* M1 #22: alias→import-path map built from the N_USE nodes, so a
|
||||
* qualified-ref codegen hint (`utf8.decoderune`) keys mod_map on the
|
||||
* dotted path the symbols are registered under, not the bare alias.
|
||||
* For single-level packages alias == path, so this is a no-op there. */
|
||||
typedef struct Use Use;
|
||||
struct Use {
|
||||
const char *alias;
|
||||
const char *path;
|
||||
Use *next;
|
||||
};
|
||||
static Use *use_map;
|
||||
|
||||
/*
|
||||
* Retained divergence (M1 #22): this map is file-global — when two
|
||||
* modules in the same unit bind the same leaf alias to different paths
|
||||
* (sha256's `import crypto.math` vs strconv's `import math`), the first
|
||||
* match wins. The checker's twin (use_path, check.c) is module-scoped
|
||||
* to fix exactly this for qualified-NAME resolution; the cgen mangle
|
||||
* hint is NOT, because it is unreachable with an ambiguous alias: a
|
||||
* cross-module qualified ref resolves only to an EXPORTED symbol, and
|
||||
* exported non-fn decls stay bare (mod_collect skips them, so the hint
|
||||
* is moot) while an exported-fn collision would require two same-leaf
|
||||
* packages to export the same fn name AND a third caller — not present
|
||||
* (the tree links cleanly). Left file-global to stay byte-identical
|
||||
* with wwstage's symmetric usehint (cgen.ww), which is also file-global
|
||||
* (rule 10). Module-scope both stages together if a real collision
|
||||
* surfaces — filed as the M1 cgen-hint twin follow-up.
|
||||
*/
|
||||
static const char *
|
||||
use_hint(const char *alias)
|
||||
{
|
||||
if (alias == NULL) return alias;
|
||||
for (Use *u = use_map; u; u = u->next)
|
||||
if (strcmp(u->alias, alias) == 0) return u->path;
|
||||
return alias;
|
||||
}
|
||||
|
||||
/* Top-level `let` map. Populated alongside mod_map; consulted by the
|
||||
* N_IDENT store path and the &-of path to route reads/writes through
|
||||
* a RIP-relative reference rather than dropping them as the (pre-
|
||||
@@ -1383,8 +1420,18 @@ static void
|
||||
mod_collect(Cg *c, Node *file)
|
||||
{
|
||||
mod_map = NULL;
|
||||
use_map = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
/* M1 #22: record alias→path for the qualified-ref hint. */
|
||||
if (d->kind == N_USE && d->str && d->usepath) {
|
||||
Use *u = amalloc(c->a, sizeof *u);
|
||||
u->alias = d->str;
|
||||
u->path = d->usepath;
|
||||
u->next = use_map;
|
||||
use_map = u;
|
||||
continue;
|
||||
}
|
||||
int isfn = (d->kind == N_FNDECL);
|
||||
int track = isfn || (d->kind == N_TYPEDECL)
|
||||
|| (d->kind == N_DEF) || (d->kind == N_LET);
|
||||
@@ -1399,8 +1446,11 @@ mod_collect(Cg *c, Node *file)
|
||||
if (decl_has_ffisym(d)) continue;
|
||||
/* `main` is the linker entry-point convention. Even when not
|
||||
* marked `export`, it must keep its bare name so w6l can
|
||||
* resolve `_start`'s `CALL main(SB)`. */
|
||||
if (d->str && strcmp(d->str, "main") == 0) continue;
|
||||
* resolve `_start`'s `CALL main(SB)`. M1 #32: only the ROOT
|
||||
* unit's main stays bare; an IMPORTED `fn main` mangles on its
|
||||
* path (closes #31's dup-main by construction). */
|
||||
if (d->str && strcmp(d->str, "main") == 0 && !d->imported)
|
||||
continue;
|
||||
Mod *m = amalloc(c->a, sizeof *m);
|
||||
m->name = d->str;
|
||||
m->module = d->module;
|
||||
@@ -4321,7 +4371,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (lu && lu->kind == TY_FN)
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
else
|
||||
/* #229: dotted-module value
|
||||
@@ -4330,7 +4380,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* same-leaf collision. */
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, opnd->str,
|
||||
opnd->lhs->str),
|
||||
use_hint(opnd->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -10181,7 +10231,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* the bareword as the hint so cross-module
|
||||
* same-leaf exports resolve correctly. */
|
||||
ins1(c, A_CALL,
|
||||
mafn(c, n->lhs->str, n->lhs->lhs->str));
|
||||
mafn(c, n->lhs->str,
|
||||
use_hint(n->lhs->lhs->str)));
|
||||
} else {
|
||||
cgexpr(c, n->lhs, locals); /* AX = fn ptr */
|
||||
ins1(c, A_CALL, areg(D_AX));
|
||||
@@ -11057,7 +11108,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* `mod.fn` address-of via N_DOT — pass the
|
||||
* module bareword as the disambiguation hint. */
|
||||
ins2(c, A_LEAQ,
|
||||
mafn(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mafn(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
{
|
||||
@@ -11073,7 +11125,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
for (s = sdefs; s; s = s->next) {
|
||||
if (strcmp(s->name, n->str) != 0)
|
||||
continue;
|
||||
if (sdef_mod_match_hint(s, n->lhs->str))
|
||||
if (sdef_mod_match_hint(s, use_hint(n->lhs->str)))
|
||||
break;
|
||||
}
|
||||
if (s == NULL) {
|
||||
@@ -11105,10 +11157,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* branch above already uses n->lhs->str via mafn. */
|
||||
if (mqop == A_MOVQ) {
|
||||
ins2(c, A_MOVQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_AX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_LEAQ,
|
||||
mahint(c, n->str, n->lhs->str), areg(D_CX));
|
||||
mahint(c, n->str, use_hint(n->lhs->str)),
|
||||
areg(D_CX));
|
||||
ins2(c, mqop, amem(D_CX, 0), areg(D_AX));
|
||||
}
|
||||
goto dot_done;
|
||||
@@ -14920,8 +14974,15 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
/* TEXT directive comes first; framesize is filled at the end. */
|
||||
Prog *text = newprog(c, A_TEXT);
|
||||
/* Mangle the label using the fn's own module as the hint — picks
|
||||
* the right entry when multiple modules export the same leaf. */
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
* the right entry when multiple modules export the same leaf. M1 #32:
|
||||
* the ROOT-unit main (imported==0) is the bare `_start` entry — emit
|
||||
* it bare directly, mirroring mod_collect's skip; without this its
|
||||
* NULL cur_mod would fall through mafn's first-leaf match onto an
|
||||
* IMPORTED package's now-registered `pkg.main`. */
|
||||
if (fn->str && strcmp(fn->str, "main") == 0 && !fn->imported)
|
||||
text->to = asym("main");
|
||||
else
|
||||
text->to = mafn(c, fn->str, c->cur_mod);
|
||||
text->from.offset = 0; /* framesize patched below */
|
||||
emit(c, text);
|
||||
|
||||
@@ -15988,7 +16049,7 @@ node_fnptr_sym(Cg *c, Node *ev)
|
||||
return NULL;
|
||||
Type *du = type_chase_named(opnd->type);
|
||||
if (du == NULL || du->kind != TY_FN) return NULL;
|
||||
return mod_mangle_fn(c, opnd->str, opnd->lhs->str);
|
||||
return mod_mangle_fn(c, opnd->str, use_hint(opnd->lhs->str));
|
||||
}
|
||||
if (opnd == NULL || opnd->kind != N_IDENT) return NULL;
|
||||
Type *ou = type_chase_named(opnd->type);
|
||||
|
||||
@@ -60,6 +60,7 @@ 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 void resolve_typedecl(Checker *c, Node *d);
|
||||
|
||||
static Type *
|
||||
@@ -84,9 +85,15 @@ resolve_typename(Checker *c, Node *n)
|
||||
size_t hl = (size_t)(dot - nm);
|
||||
if (hl < sizeof head) memcpy(head, nm, hl);
|
||||
Sym *m = scope_lookup(c->cur, head);
|
||||
if (m && (m->kind == SK_USE || m->use_alias))
|
||||
s = scope_lookup_in_module(c->cur, head,
|
||||
if (m && (m->kind == SK_USE || m->use_alias)) {
|
||||
/* 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,
|
||||
head);
|
||||
if (mk == NULL) mk = head;
|
||||
s = scope_lookup_in_module(c->cur, mk,
|
||||
dot + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s == NULL || s->kind != SK_TYPE)
|
||||
@@ -599,7 +606,10 @@ 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;
|
||||
Sym *s = scope_lookup_in_module(c->cur, n->lhs->str, n->str);
|
||||
/* 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;
|
||||
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)
|
||||
return 0;
|
||||
@@ -1343,8 +1353,14 @@ cexpr(Checker *c, Node *n)
|
||||
* same-leaf-name types from different
|
||||
* imports (`bufio.stream`/`io.stream`)
|
||||
* disambiguate to the right one. */
|
||||
/* M1 #22: symbols are keyed on the dotted
|
||||
* 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,
|
||||
n->lhs->str);
|
||||
if (mk == NULL) mk = n->lhs->str;
|
||||
Sym *fs = scope_lookup_in_module(c->cur,
|
||||
n->lhs->str, n->str);
|
||||
mk, n->str);
|
||||
if (fs)
|
||||
return n->type = fs->type;
|
||||
if (ms->kind == SK_USE) {
|
||||
@@ -2671,13 +2687,52 @@ decl_mod(Node *file, Node *d)
|
||||
{
|
||||
if (d == NULL || d->module == NULL || file == NULL) return NULL;
|
||||
for (Node *u = file->list; u; u = u->next) {
|
||||
if (u->kind == N_USE && u->str
|
||||
&& strcmp(u->str, d->module) == 0)
|
||||
/* M1 #22: a decl is imported iff some `use` directive's full
|
||||
* dotted import path equals the decl's module (now the path,
|
||||
* not the leaf). For single-level packages usepath == leaf so
|
||||
* this is unchanged; nested packages (`encoding.utf8`) match
|
||||
* here instead of on the bare leaf. */
|
||||
if (u->kind == N_USE && u->usepath
|
||||
&& strcmp(u->usepath, d->module) == 0)
|
||||
return d->module;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* 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; 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`.
|
||||
*/
|
||||
static const char *
|
||||
use_path(Node *file, const char *curmod, const char *alias)
|
||||
{
|
||||
if (file == NULL || alias == NULL) return NULL;
|
||||
const char *any = NULL;
|
||||
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);
|
||||
if (same)
|
||||
return p;
|
||||
if (any == NULL) any = p;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
/*
|
||||
* resolve_typedecl — resolve d's body into its installed TY_NAMED
|
||||
* placeholder. Reached from check_file's typedecl pass AND on demand
|
||||
@@ -2739,7 +2794,7 @@ src_imports(Node *file, const char *modtag, const char *name)
|
||||
* even though its module tag is also "fmt"; that directive
|
||||
* doesn't introduce a foreign module bareword and lib/fmt's
|
||||
* own `fn bsprintf(fmt: str, ...)` is not a shadow of it. */
|
||||
if (u->module && u->str && strcmp(u->module, u->str) == 0)
|
||||
if (u->module && u->usepath && strcmp(u->module, u->usepath) == 0)
|
||||
continue;
|
||||
/* decl_mod normalises the raw `// MODULE:` tag back to NULL
|
||||
* for primary-source N_USEs (the primary's own tag won't
|
||||
@@ -2817,7 +2872,11 @@ check_file(Checker *c, Node *file)
|
||||
* (b)/(d) membership DEFERRED to task #8 (filename-
|
||||
* keyed pulls lack import->file->symbol provenance). */
|
||||
const char *owner = decl_mod(file, d);
|
||||
if (owner && owner[0] && strcmp(d->str, owner) == 0)
|
||||
/* M1 #22: self-import ⟺ the imported path equals the
|
||||
* use's own (owning) module path. Compares paths, not
|
||||
* leaves, so nested packages are caught too. */
|
||||
if (owner && owner[0] && d->usepath
|
||||
&& strcmp(d->usepath, owner) == 0)
|
||||
err(c, d->pos, "self-import: package "
|
||||
"'%s' cannot import itself", owner);
|
||||
Sym *prev = scope_lookup_local(c->cur, d->str);
|
||||
@@ -2962,16 +3021,14 @@ check_file(Checker *c, Node *file)
|
||||
}
|
||||
c->cur_mod = NULL;
|
||||
|
||||
/* Program-global, name-only, cross-module uniqueness on `main`.
|
||||
* `main` lowers to ONE bare entry symbol, so a second top-level
|
||||
* decl named `main` (any kind, any package) collides with the
|
||||
* entry at link time — today a silent segfault / link-fail in
|
||||
* both stages. The (name, module) duplicate rejects above read a
|
||||
* cross-package `foo.main` and the bare entry as distinct, so they
|
||||
* miss this. Correct multi-main mangling (entry stays bare, the
|
||||
* rest qualify) is deferred (task #32); reject loudly meanwhile
|
||||
* (rule 7). Walks USER decls only — runs before the -T synth main
|
||||
* is appended below — so a hosted-test build never false-counts. */
|
||||
/* Program-global uniqueness on the ENTRY `main`. M1 #32: the entry
|
||||
* is the ROOT-unit main (imported==0) — it alone lowers to the bare
|
||||
* `main` symbol w6l's _start calls. An IMPORTED package's `main`
|
||||
* (imported==1) mangles on its path (`foo.bar.main`) and may coexist
|
||||
* — closing the old dup-main collision by construction (#31). Two
|
||||
* ROOT entries still collide on the bare symbol → reject loud (rule
|
||||
* 7). Walks USER decls only — runs before the -T synth main is
|
||||
* appended below — so a hosted-test build never false-counts. */
|
||||
{
|
||||
Node *firstmain = NULL;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
@@ -2980,12 +3037,14 @@ check_file(Checker *c, Node *file)
|
||||
if (d->kind != N_FNDECL && d->kind != N_LET
|
||||
&& d->kind != N_DEF && d->kind != N_TYPEDECL)
|
||||
continue;
|
||||
if (d->imported)
|
||||
continue;
|
||||
if (firstmain == NULL) {
|
||||
firstmain = d;
|
||||
continue;
|
||||
}
|
||||
err(c, d->pos, "duplicate top-level main: only the "
|
||||
"entry main may exist (task #32)");
|
||||
err(c, d->pos, "duplicate entry main: only one root "
|
||||
"main may exist (#32)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,14 +102,38 @@ skipws(Lex *l)
|
||||
* then skipped like any comment. Mirrors the removed
|
||||
* `// MODULE:` lexer directive. */
|
||||
{
|
||||
static const char dir[] = "ww:module-reset";
|
||||
static const char pre[] = "ww:module";
|
||||
size_t i = 0;
|
||||
while (dir[i] && lpeek(l, i) == dir[i])
|
||||
while (pre[i] && lpeek(l, i) == pre[i])
|
||||
i++;
|
||||
if (dir[i] == '\0') {
|
||||
if (pre[i] == '\0') {
|
||||
int nx = lpeek(l, i);
|
||||
if (nx == '\n' || nx < 0)
|
||||
l->modreset = 1;
|
||||
if (nx == '-') {
|
||||
static const char rest[] = "-reset";
|
||||
size_t j = 0;
|
||||
while (rest[j] && lpeek(l, i + j) == rest[j])
|
||||
j++;
|
||||
if (rest[j] == '\0') {
|
||||
int af = lpeek(l, i + j);
|
||||
if (af == '\n' || af < 0)
|
||||
l->modreset = 1;
|
||||
}
|
||||
} else if (nx == ' ' || nx == '\t') {
|
||||
/* `//ww:module <path>` — M1 import boundary. */
|
||||
size_t k = i;
|
||||
while (lpeek(l, k) == ' '
|
||||
|| lpeek(l, k) == '\t')
|
||||
k++;
|
||||
size_t s = k;
|
||||
int ch;
|
||||
while ((ch = lpeek(l, k)) >= 0
|
||||
&& ch != '\n' && ch != '\r'
|
||||
&& ch != ' ' && ch != '\t')
|
||||
k++;
|
||||
if (k > s)
|
||||
l->modpath = astrndup(l->a,
|
||||
l->src + l->pos + s, k - s);
|
||||
}
|
||||
}
|
||||
}
|
||||
while ((c = lpeek(l, 0)) >= 0 && c != '\n')
|
||||
@@ -403,6 +427,13 @@ lexnext(Lex *l)
|
||||
/* A `//ww:module-reset` seen in the skipped run surfaces as its own
|
||||
* token before the next real one (#16 option-B boundary reset). */
|
||||
if (l->modreset) { l->modreset = 0; EMIT(TK_MODRESET); }
|
||||
if (l->modpath) {
|
||||
const char *mp = l->modpath;
|
||||
l->modpath = NULL;
|
||||
Tok _t = (Tok){ TK_MODPATH, start, NULL, 0, {0}, TK_NONE };
|
||||
_t.text = mp; _t.tlen = strlen(mp);
|
||||
return _t;
|
||||
}
|
||||
if (!more) {
|
||||
Tok t = (Tok){ TK_EOF, start, "", 0, {0}, TK_NONE };
|
||||
return t;
|
||||
|
||||
@@ -1253,11 +1253,24 @@ 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`). */
|
||||
char pathbuf[256];
|
||||
size_t pl = 0;
|
||||
const char *leaf = expectident(p);
|
||||
while (accept(p, TK_DOT))
|
||||
for (size_t i = 0; leaf[i] && pl + 1 < sizeof pathbuf; i++)
|
||||
pathbuf[pl++] = leaf[i];
|
||||
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];
|
||||
}
|
||||
pathbuf[pl] = '\0';
|
||||
n->str = leaf;
|
||||
n->strlen = strlen(leaf);
|
||||
n->usepath = astrndup(p->a, pathbuf, pl);
|
||||
expect(p, TK_SEMI);
|
||||
return n;
|
||||
}
|
||||
@@ -1360,7 +1373,32 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
p->curmod = name;
|
||||
if (p->pathmod != 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. */
|
||||
const char *dot = strrchr(p->pathmod, '.');
|
||||
const char *last = dot ? dot + 1 : p->pathmod;
|
||||
if (strcmp(name, last) != 0) {
|
||||
errorf(p->cur.pos,
|
||||
"package %s does not match import path %s",
|
||||
name, p->pathmod);
|
||||
p->errs++;
|
||||
}
|
||||
} else {
|
||||
p->curmod = name;
|
||||
}
|
||||
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
|
||||
* root-only bare-`main` rule, #32). */
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
/* `//ww:module-reset` — bundle boundary before a package-less
|
||||
@@ -1374,6 +1412,7 @@ parsefile(Parser *p)
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
advance(p);
|
||||
p->curmod = NULL;
|
||||
p->pathmod = NULL;
|
||||
continue;
|
||||
}
|
||||
Node *attrs = parseattrs(p);
|
||||
@@ -1399,7 +1438,10 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
if (d != NULL) d->module = p->curmod;
|
||||
if (d != NULL) {
|
||||
d->module = p->curmod;
|
||||
d->imported = (p->pathmod != NULL);
|
||||
}
|
||||
if (head == NULL) head = d;
|
||||
else tail->next = d;
|
||||
tail = d;
|
||||
|
||||
@@ -104,6 +104,7 @@ tokname(Tkind k)
|
||||
case TK_ENUM: return "enum";
|
||||
case TK_MODULE: return "package";
|
||||
case TK_MODRESET: return "//ww:module-reset";
|
||||
case TK_MODPATH: return "//ww:module";
|
||||
|
||||
case TK_LPAREN: return "(";
|
||||
case TK_RPAREN: return ")";
|
||||
|
||||
23
cmd/wcc/ww.h
23
cmd/wcc/ww.h
@@ -184,6 +184,10 @@ typedef enum {
|
||||
* curmod to NULL before a package-less file's bytes
|
||||
* (#16 option-B; the package-less-entry attribution fix
|
||||
* 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
|
||||
* text carries the dotted path. */
|
||||
|
||||
TK_LAST /* sentinel for tables */
|
||||
} Tkind;
|
||||
@@ -214,6 +218,9 @@ struct Lex {
|
||||
int modreset; /* a `//ww:module-reset` directive was seen in
|
||||
* the last skipped run; lexnext emits TK_MODRESET
|
||||
* before the next real token. */
|
||||
const char *modpath; /* a `//ww:module <path>` directive was seen in
|
||||
* the last skipped run; lexnext emits TK_MODPATH
|
||||
* carrying this dotted path (M1 #22). */
|
||||
};
|
||||
|
||||
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
|
||||
@@ -340,7 +347,17 @@ struct Node {
|
||||
* decl's section in combined.ww.
|
||||
* NULL for nested nodes; only top-
|
||||
* level decls (fn/def/type/let)
|
||||
* carry it. */
|
||||
* carry it. On an N_USE 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
|
||||
* the path-keyed decl_mod match and the
|
||||
* qualified-ref codegen hint. */
|
||||
int imported; /* M1 #22: decl reached through an
|
||||
* `//ww:module <path>` import boundary
|
||||
* (vs root/primary). Gates the root-only
|
||||
* bare-`main` rule (#32). */
|
||||
};
|
||||
|
||||
Node *newnode(Arena*, Nkind, Pos);
|
||||
@@ -358,6 +375,10 @@ struct Parser {
|
||||
const char *curmod; /* most-recent `module foo;` declaration —
|
||||
* stamped onto each top-level decl that
|
||||
* follows. */
|
||||
const char *pathmod; /* M1 #22: active `//ww:module <path>` dotted
|
||||
* import path; while set, decls stamp
|
||||
* module=pathmod and imported=1, and the
|
||||
* in-file `package` clause is an assertion. */
|
||||
};
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
|
||||
@@ -218,7 +218,7 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
|
||||
}
|
||||
|
||||
static void expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *libdir);
|
||||
const char *libdir, const char *modpath);
|
||||
|
||||
/* Scan `path` for its first non-comment-non-blank line; if it starts
|
||||
* with `package <name>;` write the name into `out` (NUL-terminated)
|
||||
@@ -299,7 +299,7 @@ unit_has_package(const char *path, const char *leaf)
|
||||
* dir-enum). */
|
||||
static void
|
||||
expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
const char *libdir)
|
||||
const char *libdir, const char *modpath)
|
||||
{
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(dirpath, &files);
|
||||
@@ -318,7 +318,7 @@ expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
expand(out, fp, visited, libdir);
|
||||
expand(out, fp, visited, libdir, modpath);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
@@ -331,7 +331,7 @@ expand_dir(FILE *out, const char *dirpath, struct ImportSet *visited,
|
||||
* it). */
|
||||
static void
|
||||
expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *libdir)
|
||||
const char *libdir, const char *modpath)
|
||||
{
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
@@ -377,8 +377,13 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
fprintf(stderr, "ww: cannot find package %s\n", name);
|
||||
exit(1);
|
||||
}
|
||||
if (is_dir) expand_dir(out, ipath, visited, libdir);
|
||||
else expand(out, ipath, visited, libdir);
|
||||
/* M1 #22 (isdir-gated, rob-ratified): a package IS a directory, so
|
||||
* only DIRECTORY imports are package boundaries that path-mangle.
|
||||
* A single-file import (`import opcodes;` → opcodes.ww declaring
|
||||
* `package w6a`) is an intra-package file-split — it keeps its
|
||||
* in-file `package` clause as its module (no directive). */
|
||||
if (is_dir) expand_dir(out, ipath, visited, libdir, name);
|
||||
else expand(out, ipath, visited, libdir, NULL);
|
||||
}
|
||||
|
||||
/* #16 option-B: a package-less file's decls would otherwise inherit
|
||||
@@ -389,10 +394,19 @@ expand(FILE *out, const char *path, struct ImportSet *visited,
|
||||
* `package main`, which would main-prefix them). A packaged file's own
|
||||
* `package` decl already sets curmod, so it needs nothing — keeping
|
||||
* the directive out of every tracked combined.ww. (Task #11.) */
|
||||
{
|
||||
char pkg[128];
|
||||
if (!peek_package(path, pkg, sizeof pkg))
|
||||
fputs("//ww:module-reset\n", out);
|
||||
if (modpath != NULL && modpath[0] != '\0') {
|
||||
/* M1 (#22): an import-reached file carries its full dotted
|
||||
* import path so codegen mangles symbols on the path, not the
|
||||
* leaf `package` clause. The directive's absence is the root
|
||||
* marker (#32): root/primary files take the branch below. */
|
||||
fprintf(out, "//ww:module %s\n", modpath);
|
||||
} else {
|
||||
/* Root/primary file: reset the bundle boundary so a preceding
|
||||
* imported section's sticky pathmod (M1 #22) is cleared. A
|
||||
* package-less file then stays primary ("") as before; a
|
||||
* packaged primary's own `package` clause sets curmod fresh
|
||||
* (pathmod now NULL → real clause, not an assertion). */
|
||||
fputs("//ww:module-reset\n", out);
|
||||
}
|
||||
rewind(in);
|
||||
int ch;
|
||||
@@ -505,12 +519,12 @@ build_one(const char *src, int entry_is_dir, const char *out,
|
||||
char tpath[1024];
|
||||
int tdir = 0;
|
||||
if (locate_import(srcdir, "test", tpath, sizeof tpath, &tdir)) {
|
||||
if (tdir) expand_dir(cf, tpath, &visited, srcdir);
|
||||
else expand(cf, tpath, &visited, srcdir);
|
||||
if (tdir) expand_dir(cf, tpath, &visited, srcdir, "test");
|
||||
else expand(cf, tpath, &visited, srcdir, NULL);
|
||||
}
|
||||
}
|
||||
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir);
|
||||
else expand(cf, src, &visited, srcdir);
|
||||
if (entry_is_dir) expand_dir(cf, srcd, &visited, srcdir, NULL);
|
||||
else expand(cf, src, &visited, srcdir, NULL);
|
||||
fclose(cf);
|
||||
for (int i = 0; i < visited.n; i++) free(visited.paths[i]);
|
||||
free(visited.paths);
|
||||
|
||||
Reference in New Issue
Block a user