wcc/ww: serialize aggregate exported defs as value-less .wwi prototypes (BUG-2, #70)

The .wwi (separate-compile interface) producer could not serialize an
exported def whose initializer is a struct/array literal (N_STRUCTLIT/
N_ARRLIT) — `export def f64info: floatinfo = floatinfo{...}` aborted with
"unhandled const-expr node kind 15". Such a def is a DATA-global per the
#52 model, so its value lives once in the defining package's .o; the
interface needs only the type+symbol. Emit a value-less prototype
`export def X: T;` for aggregate-initializer defs; scalar fold-eligible
defs keep their value (the importer const-folds those). The parser gains
an optional-init arm so the importer can parse the prototype — value-less
`def X: T;` is now legal in any source, symmetric with the existing
bodyless-fn prototype `fn f();` (USER ruling: unconditional; a value-less
def with no defining .o is a loud undefined-symbol error at link, never
silent). Both stages; producer + parser fold into one commit (the
producer's output is unparseable without the parser arm).

M3-tail commit-6 prerequisite #2 (surfaced by the c6 scout). The
aggregate-def-field const-fold boundary is documented inline (#71). Gate
989_sepstructdef_run proves struct+array exported defs sep-build, link,
and run via external DATA refs, cs==ww, with a value-less .wwi.
This commit is contained in:
2026-06-16 18:04:54 +09:00
parent 2ff54c8bd4
commit 10d005ef58
8 changed files with 346 additions and 17 deletions

View File

@@ -402,9 +402,24 @@ wwi_decl(FILE *of, Node *d)
fputs(d->str, of);
fputs(": ", of);
wwi_type(of, d->lhs);
fputs(" = ", of);
wwi_expr(of, d->rhs);
fputs(";\n", of);
/* An aggregate initializer (N_STRUCTLIT/N_ARRLIT) is a DATA-
* global (#52): emit a value-LESS prototype `export def X: T;`.
* The defining package's own .o emits the struct/array DATA; the
* importer registers the def by TYPE only (the DATA-suppression
* if(sep_mode && imported)continue at cgen.c is already in place)
* and field/element reads become external LEAQ refs the linker
* fills. Boundary (rule 7): this gives up importer-side const-
* fold of an aggregate def's FIELDS — a no-op, ww never folds
* struct-literal field access, and an aggregate-def field
* demanded in a const-fold context stays a LOUD error (task #71). */
if (d->rhs && (d->rhs->kind == N_STRUCTLIT
|| d->rhs->kind == N_ARRLIT)) {
fputs(";\n", of);
} else {
fputs(" = ", of);
wwi_expr(of, d->rhs);
fputs(";\n", of);
}
break;
case N_LET:
fputs("export let ", of);

View File

@@ -1284,8 +1284,13 @@ parsedef(Parser *p, int exp)
n->str = expectident(p);
expect(p, TK_COLON);
n->lhs = parsetype(p);
expect(p, TK_ASSIGN);
n->rhs = parseexpr(p);
/* A value-LESS `def X: T;` is an interface prototype (an aggregate-
* init `.wwi` def whose DATA lives in the defining package — BUG-2 /
* task #71), mirroring parsefn's bodyless-prototype arm. rhs stays
* NULL; the checker's fold pass (`if (d->rhs)`) and cgen's emit_defs/
* emit_lets (`d->rhs == NULL` continue) already expect this shape. */
if (accept(p, TK_ASSIGN))
n->rhs = parseexpr(p);
expect(p, TK_SEMI);
n->export = exp;
return n;