w6c: remap legacy-union error tags in wwstage ? propagation

The #173 remap loop in cgtryprop gated on the tparam's explicit `!`
flag only; a LEGACY union (no marks anywhere) classifies error
variants POSITIONALLY (index 0 is success, cstage cg_variant_is_error
cgen.c:905) — so wwstage emitted no remap and propagated the callee's
raw tag into a differently-ordered caller union, a silent wrong arm
(cstage exit 7, wwstage 9 on the banked r700 row). Add the
variantiserror twin and gate the loop on it; refresh the drifted
cstage cite.

Graduates the held e2e row into the corpus (pin 1727/1156/3454) and
adds legacy-mode rows to test/lang/tryprop_tag_remap_test.ww:
reversed order both directions, slot-0 remap, str payload integrity,
a two-hop chain whose pre-fix runtime pass was double-miss tag
cancellation (the lang byteid leg pins the emitted remap blocks),
and a same-order zero-emission control.
This commit is contained in:
2026-08-08 17:11:50 +09:00
parent 74f927be16
commit dc33af6217
4 changed files with 167 additions and 6 deletions

View File

@@ -80,3 +80,115 @@ fn f_mw(which: i32) (i32 | emsg | e1) = {
case e1 => { assert(false); };
};
};
// Legacy (unmarked) unions: no variant carries `!`, so index 0 is the
// success and every other variant is an error POSITIONALLY (cstage
// cg_variant_is_error legacy arm). The remap must fire from that
// classification, not the `!` flag — the flag-only gate skipped legacy
// unions entirely and `?` propagated the callee's raw tag into a
// reordered caller union (the r700_try_rev_errs silent miscompile).
fn lg(which: i32) (i32 | str | bool) = {
if (which == 1) { return "z"; };
if (which == 2) { return false; };
return 100;
};
// caller error order (bool, str) reverses lg's (str, bool): 1->2, 2->1.
fn lf_rev(which: i32) (i64 | bool | str) = {
let v: i32 = lg(which)?;
return v: i64 + 1;
};
@test fn legacy_rev_str() void = {
match (lf_rev(1)) {
case let v: i64 => { assert(false); };
case bool => { assert(false); };
case let s: str => { assert(s.len == 1); assert(s[0] == 122u8); };
};
};
@test fn legacy_rev_bool() void = {
match (lf_rev(2)) {
case let v: i64 => { assert(false); };
case let b: bool => { assert(!b); };
case str => { assert(false); };
};
};
@test fn legacy_rev_success() void = {
match (lf_rev(0)) {
case let v: i64 => { assert(v == 101i64); };
case bool => { assert(false); };
case str => { assert(false); };
};
};
// the propagated error lands at slot 0 of the enclosing union.
fn lg0(which: i32) (i64 | str | bool) = {
if (which == 1) { return false; };
if (which == 2) { return "q"; };
return 5i64;
};
fn lf_slot0(which: i32) (bool | i64 | str) = {
let v: i64 = lg0(which)?;
return v + 1;
};
@test fn legacy_slot0() void = {
match (lf_slot0(1)) {
case let b: bool => { assert(!b); };
case i64 => { assert(false); };
case str => { assert(false); };
};
match (lf_slot0(0)) {
case bool => { assert(false); };
case let v: i64 => { assert(v == 6i64); };
case str => { assert(false); };
};
};
// two chained remaps: bool rides 2->1->2 across differently-ordered
// unions. Runtime alone can pass by double-miss cancellation (2->2->2);
// the lang byte-id leg pins the emitted remap blocks in BOTH hops.
fn n2(w: i32) (i64 | bool | str) = {
let v: i32 = lg(w)?;
return v: i64;
};
fn n3(w: i32) (u8 | str | bool) = {
let v: i64 = n2(w)?;
return v: u8;
};
@test fn legacy_nested_two_hop() void = {
match (n3(2)) {
case u8 => { assert(false); };
case str => { assert(false); };
case let b: bool => { assert(!b); };
};
match (n3(0)) {
case let v: u8 => { assert(v == 100u8); };
case str => { assert(false); };
case bool => { assert(false); };
};
};
// same error order end to end: the remap loop maps every variant to
// itself and must emit NOTHING (byte-id with the pre-remap window).
fn sctl(w: i32) (i64 | str | bool) = {
let v: i32 = lg(w)?;
return v: i64;
};
@test fn legacy_same_order() void = {
match (sctl(2)) {
case i64 => { assert(false); };
case str => { assert(false); };
case let b: bool => { assert(!b); };
};
match (sctl(0)) {
case let v: i64 => { assert(v == 100i64); };
case str => { assert(false); };
case bool => { assert(false); };
};
};

View File

@@ -0,0 +1,21 @@
//ww:run-exit 7
// Migrated from 700_e2e row 69.
package main;
fn inner(n: i32) (i32 | str | bool) = {
if (n == 0) { return "z"; };
if (n < 0) { return false; };
return n;
};
fn outer(n: i32) (i64 | bool | str) = {
let v: i32 = inner(n)?;
return v: i64 + 1000;
};
fn main() i32 = {
let r: (i64 | bool | str) = outer(-1);
match (r) {
case let n: i64 => return n: i32;
case let b: bool => { if (!b) { return 7; }; return 8; };
case let s: str => return 9;
};
return 0;
};