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

@@ -7731,6 +7731,11 @@ fn initfloatstatic(n: *syntax.node) bool = {
fn inittaggedrawstatic(u: *syntax.tinfo, n: *syntax.node) bool = {
let r: *syntax.node = initstripcast(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 != nil && (r.kind == syntax.nkind.N_TRUE
|| r.kind == syntax.nkind.N_FALSE)) { return false; };
if (u == nil || u.kind != syntax.tykind.TY_TAGGED || u.nullable != 0
|| r == nil || !variantpresent(u.params, r.type_: *syntax.tinfo)
|| syntax.typeisstr(r.type_: *syntax.tinfo)
@@ -7871,9 +7876,13 @@ fn initexprstatic(t: *syntax.tinfo, n: *syntax.node) bool = {
&& u.nullable == 0) {
if (!variantpresent(u.params, r.type_: *syntax.tinfo)) { return false; };
let ru: *syntax.tinfo = tichase(r.type_: *syntax.tinfo);
// Relocation-bearing string carriers stay on the common runtime
// path: Cstage's untyped string is not the concrete str variant at
// this classifier, so a static wwstage row would change artifacts.
if (ru != nil && (ru.kind == syntax.tykind.TY_STR
|| ru.kind == syntax.tykind.TY_UNTYPED_STR
|| ru.kind == syntax.tykind.TY_SLICE)) {
return r.kind == syntax.nkind.N_STRLIT;
return false;
};
return inittaggedrawstatic(u, r);
};
@@ -7882,6 +7891,78 @@ fn initexprstatic(t: *syntax.tinfo, n: *syntax.node) bool = {
return foldintliteral(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 publishes only the explicit prefix.
// Arrays retain the repeat because their target length is known. Recurse
// through the aggregate shapes package-init lowering owns.
fn initvalidateslicerepeats(c: *checker, want: *syntax.tinfo,
expr: *syntax.node) bool = {
let r: *syntax.node = initstripcast(expr);
let u: *syntax.tinfo = tichase(want);
if (r == nil || u == nil) { return true; };
if (u.kind == syntax.tykind.TY_SLICE
&& r.kind == syntax.nkind.N_ARRLIT) {
let e: *syntax.node = r.list;
for (e != nil) {
if (e.kind == syntax.nkind.N_FIELD
&& syntax.streq(e.str, "...")) {
importdiagprefix(e);
cerr("'...' repeat has no target length in a slice literal\n");
c.errs += 1;
return false;
};
if (!initvalidateslicerepeats(c, u.sub, e)) { return false; };
e = e.next;
};
return true;
};
if (u.kind == syntax.tykind.TY_ARRAY
&& r.kind == syntax.nkind.N_ARRLIT) {
let e: *syntax.node = r.list;
for (e != nil) {
if (!(e.kind == syntax.nkind.N_FIELD
&& syntax.streq(e.str, "..."))) {
if (!initvalidateslicerepeats(c, u.sub, e)) {
return false;
};
};
e = e.next;
};
return true;
};
if (u.kind == syntax.tykind.TY_STRUCT
&& r.kind == syntax.nkind.N_STRUCTLIT) {
let e: *syntax.node = r.list;
for (e != nil) {
let field: *syntax.tfield = u.fields;
for (field != nil) {
if (syntax.streq(e.str, field.name)) { break; };
field = field.tnext;
};
if (field != nil) {
if (!initvalidateslicerepeats(c, field.type_, e.lhs)) {
return false;
};
};
e = e.next;
};
return true;
};
if (u.kind == syntax.tykind.TY_TUPLE
&& r.kind == syntax.nkind.N_TUPLE) {
let te: *syntax.ttupleelem = u.tupleelems;
let e: *syntax.node = r.list;
for (e != nil && te != nil) {
if (!initvalidateslicerepeats(c, te.type_, e)) { return false; };
e = e.next;
te = te.tnext;
};
};
return true;
};
type initwalkitem = struct {
node: *syntax.node,
next: *initwalkitem,
@@ -8266,6 +8347,10 @@ fn initlowerpackage(c: *checker, file: *syntax.node) void = {
else { if (d.rhs != nil) {
dt = d.rhs.type_: *syntax.tinfo;
}; };
if (!initvalidateslicerepeats(c, dt, d.rhs)) {
d = d.next;
continue;
};
if (!initexprstatic(dt, d.rhs)) {
d.runtimeinit = 1;
nruntime += 1u64;
@@ -8906,8 +8991,22 @@ fn checkfile(c: *checker, file: *syntax.node) void = {
// rhs returns false silently and is left untouched).
// Stamp LAST — checkletassign consumes the pre-stamp type.
if (d.rhs != nil) {
// An explicit scalar-to-tagged cast selects the carrier.
// Folding it while retaining the tagged result type makes
// cstage consume scalar registers as an already-wide ABI
// value. Mutable package lets preserve the cast so the concrete
// carrier is boxed correctly; const keeps static-only behavior.
let foldt: *syntax.tinfo = nil;
if (d.lhs != nil) {
foldt = tichase(d.lhs.type_: *syntax.tinfo);
};
let preservetaggedcast: bool = d.op != syntax.tkind.TK_CONST
&& d.rhs.kind == syntax.nkind.N_CAST
&& foldt != nil
&& foldt.kind == syntax.tykind.TY_TAGGED
&& foldt.nullable == 0;
let dv: u64 = 0u64;
if (!foldintliteral(d.rhs, &dv)) {
if (!preservetaggedcast && !foldintliteral(d.rhs, &dv)) {
if (evaldefconst(c, d.rhs, &dv, 0)) {
stampintlit(d.rhs, dv);
};

View File

@@ -5959,11 +5959,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
&& !g.pkg[products[0].root].failed
&& !seprootiscommand(&g.pkg[products[0].root]);
if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; };
if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts,
rootpackage, publishpackage, emitasm, istest) < 0) {
sepfreeproductstaging(products, nproducts);
return 1;
};
if (warm && workdirexists
&& sepvalidateworkdirowners(g, scratch) < 0) { return 1; };
let ci: i32 = 0;
@@ -6064,6 +6059,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32,
producti += 1;
};
if (!viableproduct) { return 1; };
if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts,
rootpackage, publishpackage, emitasm, istest) < 0) {
sepfreeproductstaging(products, nproducts);
return 1;
};
// Source-derived resolution and contextual legality are complete before
// coordinator completion markers or persistent vouchers are changed.
let createdoutput: sepcreateddirs;