wcc/ww: compound OP= on a tagged index/ident is a loud reject (#20/#21)

Compound `OP=` through an index (gs[i]/a[i]) or a bare ident (g) on a
tagged union silently misbehaved: cstage dropped the index compound and
plain-stored, and BOTH stages compiled an ident compound into an add on
the tag word -- byte-identical, so the gate stayed green while the tag
was corrupted. A compound op on a whole union is nonsense.

Gate the index plain-store arm on TK_ASSIGN so a compound falls to the
existing #133 reject (wwstage's byte-id twin); add a dedicated #21 ident
reject in both stages. This closes the compound half of the tagged-payload
write class (deref #18, dot #34 already reject).

#19 (global tagged-array static-init DATA) is a separate emitter, still open.
This commit is contained in:
2026-06-14 01:14:51 +09:00
parent 769be55905
commit 33f940e17c
6 changed files with 427 additions and 2 deletions

View File

@@ -31677,6 +31677,25 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
};
// #21: a COMPOUND op on a whole tagged-union IDENT (`g OP= v`
// with g:(int|bool)) is nonsense — the ident load-combine-store
// tail below reads and writes one word of the {payload,tag} box,
// corrupting the tag. Reject loud here, the ident twin of the #18
// deref / #133 index rejects; the byte-id twin of the cstage
// guard. Plain `=` (the tagged-ident reassign arm just below) is
// untouched.
if (lhs != nil) {
if (lhs.kind == nkind.N_IDENT && n.op != tkind.TK_ASSIGN) {
let itu: *tinfo = tichase(lhs.type_: *tinfo);
if (itu != nil) {
if (itu.kind == tykind.TY_TAGGED) {
let m21: str = "ident compound on tagged not wired (#21/rule-7)\n";
os.write(2, m21.ptr, m21.len: u64);
os.exit(1);
};
};
};
};
// Tagged-union local reassignment: `r = expr;` where r has a
// tagged-union type. Delegate to cgwidentaggedstore (same path
// as cglet's tagged-init). Covers nullable fold, tagged source,