wcc/ww: tag sep-built dotted-path packages by full path not leaf (#57)
A separately-compiled package's primary body was emitted under a bare `//ww:module-reset`, so its own `package <leaf>;` clause set curmod to the leaf (e.g. utf8) while the importer spliced the .wwi under the full `//ww:module encoding.utf8` — definer mangled `utf8.X`, importer wanted `encoding.utf8.X`, unresolved. Thread the dotted path through the directive: `//ww:module-reset <path>` sets curmod to the dotted path (imported stays 0, so the root `fn main` stays bare per #32), and the body's package clause is demoted to a leaf==last-component assertion instead of overwriting curmod. Aligns sep-build to the M1 path-mangle model; only the SEP emitter changes (the combined build_one arm is untouched, so all combined byte-id gates hold). Both stages mirrored. Commit-6 broad-soak prerequisite. Gate 989_sepdotpath_run sep-builds a 2-level dotted package and proves definer==importer qualification + single-component non-vacuity, cs==ww.
This commit is contained in:
@@ -115,8 +115,33 @@ skipws(Lex *l)
|
||||
j++;
|
||||
if (rest[j] == '\0') {
|
||||
int af = lpeek(l, i + j);
|
||||
if (af == '\n' || af < 0)
|
||||
if (af == '\n' || af < 0) {
|
||||
l->modreset = 1;
|
||||
} else if (af == ' '
|
||||
|| af == '\t') {
|
||||
/* `//ww:module-reset <path>`
|
||||
* — sep primary body tagged by
|
||||
* its full dotted import path so
|
||||
* definer == importer (#57). */
|
||||
size_t k = i + j;
|
||||
while (lpeek(l, k) == ' '
|
||||
|| lpeek(l, k) == '\t')
|
||||
k++;
|
||||
size_t s = k;
|
||||
int dch;
|
||||
while ((dch = lpeek(l, k)) >= 0
|
||||
&& dch != '\n'
|
||||
&& dch != '\r'
|
||||
&& dch != ' '
|
||||
&& dch != '\t')
|
||||
k++;
|
||||
l->modreset = 1;
|
||||
if (k > s)
|
||||
l->modresetpath =
|
||||
astrndup(l->a,
|
||||
l->src + l->pos + s,
|
||||
k - s);
|
||||
}
|
||||
}
|
||||
} else if (nx == ' ' || nx == '\t') {
|
||||
/* `//ww:module <path>` — M1 import boundary. */
|
||||
@@ -426,7 +451,15 @@ lexnext(Lex *l)
|
||||
Pos start = lpos(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->modreset) {
|
||||
l->modreset = 0;
|
||||
const char *rp = l->modresetpath;
|
||||
l->modresetpath = NULL;
|
||||
/* path-carrying reset → text=path (#57); bare reset → text=NULL */
|
||||
Tok _t = (Tok){ TK_MODRESET, start, rp, rp ? strlen(rp) : 0,
|
||||
{0}, TK_NONE };
|
||||
return _t;
|
||||
}
|
||||
if (l->modpath) {
|
||||
const char *mp = l->modpath;
|
||||
l->modpath = NULL;
|
||||
|
||||
@@ -1373,17 +1373,22 @@ parsefile(Parser *p)
|
||||
advance(p);
|
||||
const char *name = expectident(p);
|
||||
expect(p, TK_SEMI);
|
||||
if (p->pathmod != NULL) {
|
||||
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. */
|
||||
const char *dot = strrchr(p->pathmod, '.');
|
||||
const char *last = dot ? dot + 1 : p->pathmod;
|
||||
* 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;
|
||||
if (strcmp(name, last) != 0) {
|
||||
errorf(p->cur.pos,
|
||||
"package %s does not match import path %s",
|
||||
name, p->pathmod);
|
||||
name, active);
|
||||
p->errs++;
|
||||
}
|
||||
} else {
|
||||
@@ -1398,6 +1403,7 @@ parsefile(Parser *p)
|
||||
if (p->cur.kind == TK_MODPATH) {
|
||||
p->pathmod = p->cur.text;
|
||||
p->curmod = p->cur.text;
|
||||
p->resetmod = NULL;
|
||||
advance(p);
|
||||
continue;
|
||||
}
|
||||
@@ -1410,9 +1416,23 @@ parsefile(Parser *p)
|
||||
* directive after a mid-file `package` would strip subsequent
|
||||
* decls to bare — that usage is deliberate-only. */
|
||||
if (p->cur.kind == TK_MODRESET) {
|
||||
/* #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
|
||||
* primary-ness and the #32 bare-main rule are intact;
|
||||
* the body's `package` clause then asserts (resetmod).
|
||||
* A bare reset is the root/package-less boundary: curmod
|
||||
* NULL → bare symbols, today's behavior. */
|
||||
const char *rp = p->cur.text;
|
||||
advance(p);
|
||||
p->curmod = NULL;
|
||||
p->pathmod = NULL;
|
||||
if (rp != NULL) {
|
||||
p->curmod = rp;
|
||||
p->resetmod = rp;
|
||||
} else {
|
||||
p->curmod = NULL;
|
||||
p->resetmod = NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Node *attrs = parseattrs(p);
|
||||
|
||||
10
cmd/wcc/ww.h
10
cmd/wcc/ww.h
@@ -221,6 +221,10 @@ struct Lex {
|
||||
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). */
|
||||
const char *modresetpath; /* a `//ww:module-reset <path>` directive
|
||||
* was seen; the next TK_MODRESET carries this
|
||||
* dotted path so the sep primary body mangles
|
||||
* on the path, not its leaf clause (#57). */
|
||||
};
|
||||
|
||||
void lexinit(Lex*, Arena*, const char *file, const char *src, u64 len);
|
||||
@@ -379,6 +383,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. */
|
||||
};
|
||||
|
||||
void parserinit(Parser*, Arena*, Lex*);
|
||||
|
||||
@@ -787,7 +787,7 @@ sep_mark_deps(struct sepgraph *g, int pi, char *inset)
|
||||
* body); FILE imports fold in (intra-package split). */
|
||||
static void
|
||||
sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
const char *searchpath, const char *modpath)
|
||||
{
|
||||
if (import_seen(visited, path)) return;
|
||||
import_add(visited, path);
|
||||
@@ -818,9 +818,15 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||
&is_dir))
|
||||
continue;
|
||||
if (!is_dir)
|
||||
sep_emit_body(out, ipath, visited, searchpath);
|
||||
sep_emit_body(out, ipath, visited, searchpath, modpath);
|
||||
}
|
||||
fputs("//ww:module-reset\n", out);
|
||||
/* #57: tag the primary body by its full dotted import path so the
|
||||
* definer mangles == the importer reference; a root build (path "")
|
||||
* stays a bare reset (keeps bare main). */
|
||||
if (modpath != NULL && modpath[0] != '\0')
|
||||
fprintf(out, "//ww:module-reset %s\n", modpath);
|
||||
else
|
||||
fputs("//ww:module-reset\n", out);
|
||||
rewind(in);
|
||||
int ch;
|
||||
while ((ch = fgetc(in)) != EOF) fputc(ch, out);
|
||||
@@ -830,14 +836,14 @@ sep_emit_body(FILE *out, const char *path, struct ImportSet *visited,
|
||||
|
||||
static void
|
||||
sep_emit_dir_body(FILE *out, const char *dir, struct ImportSet *visited,
|
||||
const char *searchpath)
|
||||
const char *searchpath, const char *modpath)
|
||||
{
|
||||
char **files = NULL;
|
||||
int n = enumerate_dir_ww(dir, &files);
|
||||
for (int i = 0; i < n; i++) {
|
||||
char fp[1024];
|
||||
snprintf(fp, sizeof fp, "%s/%s", dir, files[i]);
|
||||
sep_emit_body(out, fp, visited, searchpath);
|
||||
sep_emit_body(out, fp, visited, searchpath, modpath);
|
||||
free(files[i]);
|
||||
}
|
||||
free(files);
|
||||
@@ -876,9 +882,11 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
||||
}
|
||||
struct ImportSet bodyvisit = {0};
|
||||
if (g->pkg[pi].is_dir)
|
||||
sep_emit_dir_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
sep_emit_dir_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
|
||||
g->pkg[pi].path);
|
||||
else
|
||||
sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath);
|
||||
sep_emit_body(u, g->pkg[pi].entry, &bodyvisit, searchpath,
|
||||
g->pkg[pi].path);
|
||||
for (int i = 0; i < bodyvisit.n; i++) free(bodyvisit.paths[i]);
|
||||
free(bodyvisit.paths);
|
||||
fclose(u);
|
||||
|
||||
Reference in New Issue
Block a user