ww: finish initialization validation parity

This commit is contained in:
2026-08-14 20:03:11 +09:00
parent 25da54936d
commit c59a7c11de
26 changed files with 528 additions and 143 deletions

View File

@@ -3193,6 +3193,10 @@ static int
init_tagged_raw_static(Type *u, Node *n)
{
Node *r = init_strip_cast(n);
/* Cstage keeps true/false as untyped-bool, distinct from the concrete
* bool variant selected by runtime boxing. Keep boolean payloads on that
* runtime path in both stages so package artifacts remain identical. */
if (r != NULL && (r->kind == N_TRUE || r->kind == N_FALSE)) return 0;
Type *ru = type_chase_named(r ? r->type : NULL);
u64 ignored;
return u != NULL && u->kind == TY_TAGGED && !u->nullable
@@ -3336,8 +3340,13 @@ init_expr_static(Type *t, Node *n)
if (u != NULL && u->kind == TY_TAGGED && !u->nullable) {
Type *ru = type_chase_named(r->type);
if (!variant_present(u->params, r->type)) return 0;
if (ru != NULL && (ru->kind == TY_STR || ru->kind == TY_SLICE))
return r->kind == N_STRLIT;
/* String carriers need a relocation-bearing tagged row. Cstage's
* untyped string is not the concrete str variant here; keep every
* such mutable value on the common runtime path rather than let one
* stage classify the same spelling as static data. */
if (ru != NULL && (ru->kind == TY_STR
|| ru->kind == TY_UNTYPED_STR || ru->kind == TY_SLICE))
return 0;
return init_tagged_raw_static(u, r);
}
if (init_fnptr_static(r)) return 1;
@@ -3345,6 +3354,67 @@ init_expr_static(Type *t, Node *n)
return fold_int_literal(r, &ignored);
}
/* A slice literal has no declared element count for WW's trailing `...`
* repeat to fill. Keep this a checker-owned semantic rejection when a
* mutable package let moves from static data to runtime initialization;
* otherwise the backing counter drops the marker and silently publishes the
* explicit prefix. Array repeats remain valid because their target length is
* known. Recurse through the aggregate shapes package-init lowering owns so
* a nested slice cannot bypass the same rule. */
static int
init_validate_slice_repeats(Checker *c, Type *want, Node *expr)
{
Node *r = init_strip_cast(expr);
Type *u = type_chase_named(want);
if (r == NULL || u == NULL) return 0;
if (u->kind == TY_SLICE && r->kind == N_ARRLIT) {
for (Node *e = r->list; e; e = e->next) {
if (e->kind == N_FIELD && e->str != NULL
&& strcmp(e->str, "...") == 0) {
err(c, e->pos, "'...' repeat has no target length "
"in a slice literal");
return -1;
}
if (init_validate_slice_repeats(c, u->sub, e) < 0)
return -1;
}
return 0;
}
if (u->kind == TY_ARRAY && r->kind == N_ARRLIT) {
for (Node *e = r->list; e; e = e->next) {
if (e->kind == N_FIELD && e->str != NULL
&& strcmp(e->str, "...") == 0)
continue;
if (init_validate_slice_repeats(c, u->sub, e) < 0)
return -1;
}
return 0;
}
if (u->kind == TY_STRUCT && r->kind == N_STRUCTLIT) {
for (Node *e = r->list; e; e = e->next) {
Tfield *field = NULL;
for (Tfield *f = u->fields; f; f = f->next)
if (e->str != NULL && f->name != NULL
&& strcmp(e->str, f->name) == 0) {
field = f;
break;
}
if (field != NULL
&& init_validate_slice_repeats(c, field->type,
e->lhs) < 0)
return -1;
}
return 0;
}
if (u->kind == TY_TUPLE && r->kind == N_TUPLE) {
Tparam *p = u->params;
for (Node *e = r->list; e && p; e = e->next, p = p->next)
if (init_validate_slice_repeats(c, p->type, e) < 0)
return -1;
}
return 0;
}
struct initwalkitem {
Node *node;
struct initwalkitem *next;
@@ -3665,6 +3735,8 @@ init_lower_package(Checker *c, Node *file)
return -1;
}
nvar++;
if (init_validate_slice_repeats(c, d->type, d->rhs) < 0)
continue;
if (!init_expr_static(d->type, d->rhs)) {
d->runtimeinit = 1;
nruntime++;
@@ -4601,8 +4673,19 @@ check_file(Checker *c, Node *file)
* returns 0 silently and is left untouched).
* Stamp LAST — the assignability check above
* consumes the pre-stamp cexpr type. */
/* An explicit scalar-to-tagged cast is also the carrier
* selection. Folding it to an integer literal while keeping
* the tagged result type makes cgen consume scalar registers
* as an already-wide tagged ABI value. Mutable package lets
* preserve the cast so the concrete carrier is boxed correctly;
* const remains on its established static-only path. */
Type *foldt = type_chase_named(d->type);
int preserve_tagged_cast = d->op != TK_CONST && d->rhs
&& d->rhs->kind == N_CAST && foldt != NULL
&& foldt->kind == TY_TAGGED && !foldt->nullable;
u64 dv;
if (d->rhs && !fold_int_literal(d->rhs, &dv)
if (d->rhs && !preserve_tagged_cast
&& !fold_int_literal(d->rhs, &dv)
&& eval_def_const(c, d->rhs, &dv, 0))
stamp_intlit(c, d->rhs, dv);
} else if (d->type && d->type->kind == TY_ARRAY