ww: preserve command package identities

This commit is contained in:
2026-08-13 03:37:08 +09:00
parent 10d24a3237
commit 4377634bb9
10 changed files with 348 additions and 43 deletions

View File

@@ -31,13 +31,14 @@ struct importin {
static Node *
parseinput(Arena *a, const char *file, char *buf, u64 len,
const char *mod, const char *testsupport, int *bad)
const char *mod, const char *testsupport, int commandpackage, int *bad)
{
Lex l;
Parser p;
lexinit(&l, a, file, buf, len);
parserinit(&p, a, &l);
p.testmodule = testsupport;
p.commandpackage = commandpackage;
if (mod != NULL) {
p.pathmod = mod;
p.curmod = mod;
@@ -66,6 +67,7 @@ main(int argc, char **argv)
const char *testsupport = NULL;
int testmode = 0;
int testpackage = 0;
int commandpackage = 0;
int entrymode = 0;
int sepmode = 0; /* -c: #22 M3 separate-compile / primary-
* only codegen (emit imported==0 decls
@@ -86,6 +88,8 @@ main(int argc, char **argv)
testmode = 1;
} else if (strcmp(a, "--test-package") == 0) {
testpackage = 1;
} else if (strcmp(a, "--command-package") == 0) {
commandpackage = 1;
} else if (strcmp(a, "--entry") == 0) {
entrymode = 1;
} else if (strcmp(a, "--test-support-module") == 0) {
@@ -115,7 +119,7 @@ main(int argc, char **argv)
}
}
if (src == NULL) {
fputs("usage: w6c [-T|--test-package] [--entry] [-c] [-I out.wwi] "
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [-c] [-I out.wwi] "
"[--import path dep.wwi]... [-o out.s] file.ww\n", stderr);
return 2;
}
@@ -123,8 +127,8 @@ main(int argc, char **argv)
fputs("w6c: --import requires -c\n", stderr);
return 2;
}
if ((entrymode || testpackage) && !sepmode) {
fputs("w6c: --entry and --test-package require -c\n", stderr);
if ((entrymode || testpackage || commandpackage) && !sepmode) {
fputs("w6c: --entry, --test-package, and --command-package require -c\n", stderr);
return 2;
}
if (testmode && testpackage) {
@@ -162,7 +166,7 @@ main(int argc, char **argv)
}
int bad = 0;
Node *f = parseinput(a, imports[i].file, imports[i].buf,
imports[i].len, imports[i].path, testsupport, &bad);
imports[i].len, imports[i].path, testsupport, 0, &bad);
if (bad) return 1;
appendnodes(&head, &tail, f->list);
}
@@ -174,7 +178,11 @@ main(int argc, char **argv)
return 1;
}
int bad = 0;
Node *file = parseinput(a, src, buf, len, NULL, testsupport, &bad);
/* --entry classifies an ordinary selected command. The separate
* --command-package marker carries the same parser-only fact for command
* test variants whose code generation must not expose bare main. */
Node *file = parseinput(a, src, buf, len, NULL, testsupport,
commandpackage || entrymode, &bad);
if (bad) return 1;
if (head != NULL) {
tail->next = file->list;

View File

@@ -1575,7 +1575,12 @@ parsefile(Parser *p)
&& strcmp(p->testmodule, "__wwtest") == 0
&& strcmp(active, "__wwtest") == 0
&& strcmp(name, "test") == 0;
if (strcmp(name, last) != 0 && !testsupport) {
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);

View File

@@ -381,6 +381,8 @@ struct Parser {
* component) instead of overwriting curmod. */
const char *testmodule; /* hidden package-driver alias for toolchain
* `package test`; NULL outside that compile */
int commandpackage; /* selected command family: package main/main_test
* validates kind without replacing resetmod identity */
};
void parserinit(Parser*, Arena*, Lex*);

View File

@@ -638,6 +638,37 @@ sep_import_base_valid(const char *path)
return 0;
}
/* Go command packages retain their canonical import identity while declaring
* package main. An external command-test variant declares main_test. These
* declarations classify package kind but never participate in interning. */
static int
sep_command_declared_name(const struct seppkg *p)
{
return p->variant == SEP_VARIANT_EXTERNAL
? strcmp(p->name, "main_test") == 0
: strcmp(p->name, "main") == 0;
}
static int
sep_forbidden_command_import(const struct sepgraph *g, int importer, int dep)
{
const struct seppkg *from = &g->pkg[importer];
const struct seppkg *to = &g->pkg[dep];
if (strcmp(to->name, "main") != 0 || to->role != SEP_ROLE_NORMAL)
return 0;
/* An external test's exact import of its colocated command production is
* test-variant wiring, not a general source-importable command edge. */
return !(from->variant == SEP_VARIANT_EXTERNAL
&& sep_command_declared_name(from)
&& strcmp(from->canon, to->canon) == 0);
}
static int
sep_command_compiler_marker(const struct sepgraph *g, int pi)
{
return sep_command_declared_name(&g->pkg[pi])
&& !g->pkg[pi].link_entry;
}
/* Bind the canonical ordinary import identity of one provisional directory
* action. The action's compiler path is derived from that base and its semantic
* variant; neither requested-root state nor artifact naming participates. */
@@ -1223,7 +1254,9 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file,
}
int self = strcmp(canon, g->pkg[pi].canon) == 0;
free(canon);
if (self && sep_external_production_name(&g->pkg[pi], name, 1))
if (self
&& (sep_external_production_name(&g->pkg[pi], name, 1)
|| sep_command_declared_name(&g->pkg[pi])))
external_production = 1;
if (self && !external_production) {
const char *owner = g->pkg[pi].path[0]
@@ -1409,7 +1442,8 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
&& !g->pkg[pi].test_support) {
const char *dot = strrchr(g->pkg[pi].path, '.');
const char *leaf = dot ? dot + 1 : g->pkg[pi].path;
if (strcmp(g->pkg[pi].name, leaf) != 0) {
if (strcmp(g->pkg[pi].name, leaf) != 0
&& !sep_command_declared_name(&g->pkg[pi])) {
fprintf(stderr,
"ww: package %s does not match import path %s\n",
g->pkg[pi].name, g->pkg[pi].path);
@@ -1461,11 +1495,20 @@ sep_load_pkg(struct sepgraph *g, int pi, int context)
/* Mark before recursion so a source cycle terminates here; topo emits the
* stable cycle diagnostic after all direct bindings are known. */
g->pkg[pi].context_state[context] = 2;
for (int k = 0; k < g->pkg[pi].ndeps; k++)
if (sep_load_pkg(g, g->pkg[pi].deps[k], context) < 0) {
for (int k = 0; k < g->pkg[pi].ndeps; k++) {
int dep = g->pkg[pi].deps[k];
if (sep_load_pkg(g, dep, context) < 0) {
g->pkg[pi].failed = 1;
return -1;
}
if (dep != pi && sep_forbidden_command_import(g, pi, dep)) {
fprintf(stderr,
"ww: package %s is a program, not an importable package\n",
g->pkg[dep].path[0] ? g->pkg[dep].path : g->pkg[dep].canon);
g->pkg[pi].failed = 1;
return -1;
}
}
return 0;
}
@@ -1677,7 +1720,8 @@ sep_finalize_directory_identities(struct sepgraph *g)
int support_alias = p->role == SEP_ROLE_TEST_SUPPORT
&& strcmp(p->path, SEP_TEST_SUPPORT_MODULE) == 0
&& strcmp(p->name, "test") == 0;
if (!support_alias && strcmp(p->name, leaf) != 0) {
if (!support_alias && strcmp(p->name, leaf) != 0
&& !sep_command_declared_name(p)) {
fprintf(stderr, "ww: package %s does not match import path %s\n",
p->name, p->path);
return -1;
@@ -2437,6 +2481,8 @@ build_one_sep_impl(const char *src, int entry_is_dir,
if (g->pkg[pi].variant == SEP_VARIANT_SAME_TEST
|| g->pkg[pi].variant == SEP_VARIANT_EXTERNAL)
cargv[cpos++] = "--test-package";
if (sep_command_compiler_marker(g, pi))
cargv[cpos++] = "--command-package";
if (g->pkg[pi].link_entry)
cargv[cpos++] = "--entry";
if (g->pkg[pi].test_support) {