selfhost/cmd/wcc: type-key tagged variant match (#66, Phase-N step 3)
The user-ruled B-full semantic change: flip tagged-union variant matching from surface-NAME to TYPE-identity (typeeq over tinfo.params), mirroring cstage cg_variant_match (cmd/w6c/cgen.c:451). A cross-module `a.T` != `b.T` and `type linerr=!str` != str are now distinguished by the per-decl TY_NAMED pointer (Phase-N #64). ww has no type_assignable, so the untyped/loose arm keeps the str/slice shape fallback (rule-10 align-down). The 5 helpers (flatvariantidx, flatslicevariantidx, taggedvariantindex, cgtagvariantidx, cgmatch dispatch) flip; nomem propagation (NAMED-name scan, no source value) and the f64 widen arm (float-kind classification, no pattern node) are not arm-by-value discrimination and stay name/kind-keyed. The flip requires value nodes to carry nominal identity. exprtype's N_STRUCTLIT arm stamped the flattened body, so `overflow{}` (overflow=!void) got TY_VOID and missed its variant -- fixed to stamp the per-decl NAMED (mktname(lhs.str) -> tinfofornode reuses the #64 NAMED build/cache, same ptr the union variant resolved to), mirroring the N_CAST/N_IDENT arms + cstage. Returns the body node unchanged (only e.type_ rides NAMED); struct-lit layout is unaffected -- cgstructlitfill is structlookup(name)-keyed, never reads NAMED.fields. The fix now hits all `T{}` stamps, kept byte-id by the #63/#65 structural-walker peels. 931_variant_typekey_run: table-driven, both stages, /tmp-isolated. Two rows widen an alias-FIRST variant from a call (no surface name): `(linerr|str)` str-via-call -> idx 1, `(ec|i32)` i32-via-call -> idx 1. Empirically discriminating: FAILS pre-flip (wwstage falls to the leading-shape variant, exit 10; cstage exit 0) and PASSES post-flip -- locking in the capability byte-id can't reach (the corpus has no name-key/type-key-disagreeing co-variant, which is why name-keying survived). make test 134/134 (byte-id 990-997 green; 995 self-rebuild green).
This commit is contained in:
7
Makefile
7
Makefile
@@ -261,6 +261,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_modcall_widen_slice \
|
||||
$(BIN)/test_match_4arm_cross_module \
|
||||
$(BIN)/test_match_4arm_cross_module_run \
|
||||
$(BIN)/test_variant_typekey_run \
|
||||
$(BIN)/test_enum_modshadow \
|
||||
$(BIN)/test_struct_modshadow \
|
||||
$(BIN)/test_def_modshadow \
|
||||
@@ -785,6 +786,12 @@ $(BIN)/test_match_4arm_cross_module_run: test/wcc/929_match_4arm_cross_module_ru
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_variant_typekey_run: test/wcc/931_variant_typekey_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_use_promote_alias: test/wcc/699_use_promote_alias.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
|
||||
@@ -9225,7 +9225,26 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (ms != nil) { if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||
let tn: *node = ms.decl.lhs;
|
||||
if (tn != nil) {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
// #66 Phase-N step 3: stamp e.type_ to the NOMINAL
|
||||
// per-decl NAMED, not the flattened body. `overflow{}`
|
||||
// where `type overflow = !void` must carry
|
||||
// NAMED(overflow) so the typeeq variant match
|
||||
// (cgenutil flatvariantidx) selects the overflow arm
|
||||
// instead of falling to the scalar shape fallback;
|
||||
// stamping tinfofornode(tn) gave the body (TY_VOID)
|
||||
// and lost nominal identity. Resolve through a
|
||||
// synthesized TNAME to reuse tinfofornode's TY_NAMED
|
||||
// build/cache (check.ww:1157) — the SAME NAMED ptr
|
||||
// the union variant resolved to. Mirrors cstage
|
||||
// resolving overflow{} to the overflow Type, and ww's
|
||||
// own N_CAST / N_IDENT arms which already stamp NAMED.
|
||||
// Return the body node tn unchanged: byte-id rides
|
||||
// e.type_ (cgen), while the checker's AST-level
|
||||
// assign/return checks keep their prior input.
|
||||
let tnm: *node = mktname(c, e.lhs.str);
|
||||
let nti: *tinfo = tinfofornode(c, tnm);
|
||||
if (nti != nil) { e.type_ = nti: *void; }
|
||||
else { e.type_ = tinfofornode(c, tn): *void; };
|
||||
return tn;
|
||||
};
|
||||
}; }; };
|
||||
@@ -12720,19 +12739,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
// a single canonicalization. Same shape of fix as nodeisstr.
|
||||
let resolved: *node = resolvetagged(c, tagged);
|
||||
if (resolved != nil) { tagged = resolved; };
|
||||
let wantname: str = rhstargetname(c, rhs);
|
||||
if (wantname.len > 0) {
|
||||
let r: i32 = flatvariantidx(c, tagged, wantname);
|
||||
if (r >= 0) { return r; };
|
||||
};
|
||||
// #66 Phase-N step 3: match the value's stamped type against the
|
||||
// variant types by typeeq (flatvariantidx), replacing the
|
||||
// rhstargetname surface-name compare. Untyped/loose values (whose
|
||||
// .type_ is untyped_* and can't typeeq a concrete variant) return
|
||||
// -1 here and drop to the str/slice shape scan below — ww has no
|
||||
// type_assignable to mirror cg_variant_match's untyped-src arm.
|
||||
let r: i32 = flatvariantidx(c, tagged, rhs);
|
||||
if (r >= 0) { return r; };
|
||||
// Shape fallback: classify rhs as (str, slice, scalar/other) and
|
||||
// pick the first variant of matching shape. Cstage's type_eq
|
||||
// distinguishes a `[]u8` arm from a `u8` arm at type-build; the
|
||||
// name-only flatvariantidx pass above can't see `[]T`, so without
|
||||
// the slice axis a (u8 | []u8) widen / match collapses every
|
||||
// non-str rhs onto the leading scalar variant (task #19). Walks
|
||||
// the spread-flattened list so a `(...inner | str)` outer agrees
|
||||
// with the inner's str / slice positions.
|
||||
// pick the first variant of matching shape. Stands in for cstage's
|
||||
// type_assignable on an untyped src — the typeeq pass above can't
|
||||
// match untyped_* against a concrete variant, and the slice axis
|
||||
// keeps a (u8 | []u8) widen off the leading scalar variant (task
|
||||
// #19). Walks the spread-flattened list so a `(...inner | str)`
|
||||
// outer agrees with the inner's str / slice positions.
|
||||
let wantstr: bool = nodeisstr(c, rhs);
|
||||
let wantslice: bool = nodeisslice(c, rhs);
|
||||
let v: *node = tagged.list;
|
||||
@@ -12769,118 +12790,76 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatvariantidx — walk `tagged`'s variant list (with spread `...inner`
|
||||
// expansion) and return the flat 0-based index where `want` matches.
|
||||
// Mirrors check.c's spread flatten at type resolution: an outer
|
||||
// `(...inner | T)` has the inner's variants inlined in declaration
|
||||
// order, so the tag indices stay in sync between cstage (which
|
||||
// resolves types upfront) and wwstage (which doesn't). Returns -1 if
|
||||
// no variant matches.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, want: str) i32 = {
|
||||
// flatvariantidx — flat 0-based index of the variant whose type matches
|
||||
// the pattern node `pat`, by typeeq on the stamped tinfos. Reads the
|
||||
// pre-flattened variant chain off tinfo.params (#61a — `...inner`
|
||||
// spreads already inlined in declaration order); peels TY_NAMED then
|
||||
// gates TY_TAGGED.
|
||||
//
|
||||
// #66 Phase-N step 3 (THE FLIP, user-ruled B-full): match by
|
||||
// typeeq(p.type_, pat.type_) — nominal identity carried by the per-decl
|
||||
// TY_NAMED ptr — instead of the surface-name compare. So `type linerr =
|
||||
// !str` ≠ str and a cross-module `a.T` ≠ `b.T` are now distinguished.
|
||||
// Mirrors cstage cg_variant_match (cmd/w6c/cgen.c:451): both-NAMED →
|
||||
// ptr-id (typeeq, typ.ww:514), one-NAMED → kind mismatch → false. ww has
|
||||
// no type_assignable, so the untyped/loose arm (cg_variant_match's first
|
||||
// branch) lives in the caller's str/slice shape fallback, not here.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
if (pat == nil) { return -1; };
|
||||
let want: *tinfo = pat.type_: *tinfo;
|
||||
if (want == nil) { return -1; };
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (iv.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.str, want)) {
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
for (p != nil) {
|
||||
if (typeeq(p.type_, want)) { return idx; };
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatslicevariantidx — flat 0-based index of the first slice-shape
|
||||
// variant in `tagged` (`...inner` spread expanded). When `elem` is an
|
||||
// N_TNAME, prefer a `[]<elem.str>` variant; falls back to the first
|
||||
// slice slot if no element match is found. Cstage walks resolved
|
||||
// Type pointers and dispatches via cg_tag_for_variant / type_eq;
|
||||
// wwstage's name-keyed flatvariantidx can't see a `[]u8` variant
|
||||
// (pat.str == ""), collapsing every (scalar | []T) match arm and
|
||||
// widen-to-tagged call onto tag 0. Task #19. Returns -1 when no
|
||||
// slice variant exists.
|
||||
// flatslicevariantidx — flat 0-based index of a slice-shape variant in
|
||||
// `tagged`. Prefers the variant whose element typeeq's the pattern
|
||||
// element `elem`; falls back to the first slice-shape slot when no exact
|
||||
// element match is found (the untyped/loose arm — ww has no
|
||||
// type_assignable). Reads the flattened tinfo.params chain (#61a); peels
|
||||
// TY_NAMED then gates TY_TAGGED. The slice axis exists because a scalar-
|
||||
// vs-`[]T` distinction has no surface name to key on (task #19).
|
||||
//
|
||||
// #66 Phase-N step 3: element compare flips from surface-name to
|
||||
// typeeq(p.type_.sub, elem.type_). Mirrors cstage cg_tag_for_variant
|
||||
// over Type->params. Returns -1 when no slice variant exists.
|
||||
fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
let elemname: str;
|
||||
elemname.ptr = nil; elemname.len = 0;
|
||||
if (elem != nil) {
|
||||
if (elem.kind == nkind.N_TNAME) { elemname = elem.str; };
|
||||
};
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let want: *tinfo = nil;
|
||||
if (elem != nil) { want = elem.type_: *tinfo; };
|
||||
let fallback: i32 = -1;
|
||||
let v: *node = tagged.list;
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (isslicetype(c, iv)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (iv.kind == nkind.N_TSLICE) {
|
||||
if (iv.lhs != nil) {
|
||||
if (iv.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (isslicetype(c, v)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (v.kind == nkind.N_TSLICE) {
|
||||
if (v.lhs != nil) {
|
||||
if (v.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
for (p != nil) {
|
||||
let vt: *tinfo = p.type_;
|
||||
if (vt != nil) {
|
||||
if (typeisslice(vt)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (want != nil) {
|
||||
let su: *tinfo = vt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su != nil) {
|
||||
if (typeeq(su.sub, want)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v = v.next;
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return fallback;
|
||||
@@ -13474,15 +13453,36 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz
|
||||
let fkind: i32 = exprfloatkind(c, src);
|
||||
if (fkind != 0) {
|
||||
let fmov: str = "MOVSD";
|
||||
let fname: str = "f64";
|
||||
if (fkind == 1) { fmov = "MOVSS"; fname = "f32"; };
|
||||
if (fkind == 1) { fmov = "MOVSS"; };
|
||||
cgexpr(c, src);
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
let ftag: i32 = flatvariantidx(c, dt, fname);
|
||||
// #66 Phase-N step 3: the float arm has no pattern node to ride
|
||||
// the typeeq flatvariantidx path, so pick the variant by float
|
||||
// kind (f32 vs f64) over tinfo.params — a shape classification
|
||||
// like the slice axis, not nominal identity.
|
||||
let wantf32: bool = (fkind == 1);
|
||||
let ftag: i32 = -1;
|
||||
let fti: *tinfo = dt.type_: *tinfo;
|
||||
for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; };
|
||||
if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) {
|
||||
let fp: *tparam = fti.params;
|
||||
let fidx: i32 = 0;
|
||||
for (fp != nil) {
|
||||
let fvt: *tinfo = fp.type_;
|
||||
for (fvt != nil && fvt.kind == tykind.TY_NAMED) { fvt = fvt.under; };
|
||||
if (fvt != nil) {
|
||||
if (typeisfloat(fvt)) {
|
||||
if (typeisf32(fvt) == wantf32) { ftag = fidx; break; };
|
||||
};
|
||||
};
|
||||
fp = fp.tnext;
|
||||
fidx += 1;
|
||||
};
|
||||
}; };
|
||||
if (ftag < 0) { ftag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(ftag: i64);
|
||||
@@ -14116,17 +14116,16 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
// `is []T` / `as []T` — slice-shape variant lookup routes through
|
||||
// the shape-aware helper so non-N_TNAME variant nodes (which
|
||||
// flatvariantidx's name key can't see) resolve. Task #19.
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
// strict typeeq below doesn't. Task #19; #66 refresh.
|
||||
if (vt.kind == nkind.N_TSLICE) {
|
||||
return flatslicevariantidx(c, tagged, vt.lhs);
|
||||
};
|
||||
let want: str;
|
||||
want.ptr = nil; want.len = 0;
|
||||
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
||||
if (want.len == 0) { return -1; };
|
||||
return flatvariantidx(c, tagged, want);
|
||||
// #66 Phase-N step 3: match by typeeq on vt's stamped tinfo
|
||||
// (flatvariantidx), not vt's surface name.
|
||||
return flatvariantidx(c, tagged, vt);
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
@@ -15119,7 +15118,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (scrutt.kind == nkind.N_TTAGGED) {
|
||||
let r: i32 = -1;
|
||||
if (pat.kind == nkind.N_TNAME) {
|
||||
r = flatvariantidx(c, scrutt, pat.str);
|
||||
r = flatvariantidx(c, scrutt, pat);
|
||||
} else { if (pat.kind == nkind.N_TSLICE) {
|
||||
// `case let s: []T =>` — pat.str is empty
|
||||
// because the variant is a composite, so
|
||||
@@ -20495,7 +20494,28 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||
// #66 Phase-N step 3: nomem propagation has no
|
||||
// pattern node, so it can't ride the typeeq
|
||||
// flatvariantidx path. cstage passes the ty_nomem
|
||||
// singleton to cg_tag_for_variant; the wwstage cgen
|
||||
// holds no tinfo singleton, so find the nomem
|
||||
// variant by its NAMED name over tinfo.params.
|
||||
let nidx: i32 = -1;
|
||||
let nti: *tinfo = nil;
|
||||
if (c.fnret != nil) { nti = c.fnret.type_: *tinfo; };
|
||||
for (nti != nil && nti.kind == tykind.TY_NAMED) { nti = nti.under; };
|
||||
if (nti != nil) { if (nti.kind == tykind.TY_TAGGED) {
|
||||
let np: *tparam = nti.params;
|
||||
let nidx2: i32 = 0;
|
||||
for (np != nil) {
|
||||
let nvt: *tinfo = np.type_;
|
||||
if (nvt != nil) {
|
||||
if (variantnamematch(nvt.name, "nomem")) { nidx = nidx2; break; };
|
||||
};
|
||||
np = np.tnext;
|
||||
nidx2 += 1;
|
||||
};
|
||||
}; };
|
||||
if (nidx < 0) { nidx = 1; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(nidx: i64);
|
||||
|
||||
@@ -117,17 +117,16 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
// `is []T` / `as []T` — slice-shape variant lookup routes through
|
||||
// the shape-aware helper so non-N_TNAME variant nodes (which
|
||||
// flatvariantidx's name key can't see) resolve. Task #19.
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
// strict typeeq below doesn't. Task #19; #66 refresh.
|
||||
if (vt.kind == nkind.N_TSLICE) {
|
||||
return flatslicevariantidx(c, tagged, vt.lhs);
|
||||
};
|
||||
let want: str;
|
||||
want.ptr = nil; want.len = 0;
|
||||
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
||||
if (want.len == 0) { return -1; };
|
||||
return flatvariantidx(c, tagged, want);
|
||||
// #66 Phase-N step 3: match by typeeq on vt's stamped tinfo
|
||||
// (flatvariantidx), not vt's surface name.
|
||||
return flatvariantidx(c, tagged, vt);
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
@@ -1120,7 +1119,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (scrutt.kind == nkind.N_TTAGGED) {
|
||||
let r: i32 = -1;
|
||||
if (pat.kind == nkind.N_TNAME) {
|
||||
r = flatvariantidx(c, scrutt, pat.str);
|
||||
r = flatvariantidx(c, scrutt, pat);
|
||||
} else { if (pat.kind == nkind.N_TSLICE) {
|
||||
// `case let s: []T =>` — pat.str is empty
|
||||
// because the variant is a composite, so
|
||||
|
||||
@@ -667,7 +667,28 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||
// #66 Phase-N step 3: nomem propagation has no
|
||||
// pattern node, so it can't ride the typeeq
|
||||
// flatvariantidx path. cstage passes the ty_nomem
|
||||
// singleton to cg_tag_for_variant; the wwstage cgen
|
||||
// holds no tinfo singleton, so find the nomem
|
||||
// variant by its NAMED name over tinfo.params.
|
||||
let nidx: i32 = -1;
|
||||
let nti: *tinfo = nil;
|
||||
if (c.fnret != nil) { nti = c.fnret.type_: *tinfo; };
|
||||
for (nti != nil && nti.kind == tykind.TY_NAMED) { nti = nti.under; };
|
||||
if (nti != nil) { if (nti.kind == tykind.TY_TAGGED) {
|
||||
let np: *tparam = nti.params;
|
||||
let nidx2: i32 = 0;
|
||||
for (np != nil) {
|
||||
let nvt: *tinfo = np.type_;
|
||||
if (nvt != nil) {
|
||||
if (variantnamematch(nvt.name, "nomem")) { nidx = nidx2; break; };
|
||||
};
|
||||
np = np.tnext;
|
||||
nidx2 += 1;
|
||||
};
|
||||
}; };
|
||||
if (nidx < 0) { nidx = 1; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(nidx: i64);
|
||||
|
||||
@@ -2194,19 +2194,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
// a single canonicalization. Same shape of fix as nodeisstr.
|
||||
let resolved: *node = resolvetagged(c, tagged);
|
||||
if (resolved != nil) { tagged = resolved; };
|
||||
let wantname: str = rhstargetname(c, rhs);
|
||||
if (wantname.len > 0) {
|
||||
let r: i32 = flatvariantidx(c, tagged, wantname);
|
||||
if (r >= 0) { return r; };
|
||||
};
|
||||
// #66 Phase-N step 3: match the value's stamped type against the
|
||||
// variant types by typeeq (flatvariantidx), replacing the
|
||||
// rhstargetname surface-name compare. Untyped/loose values (whose
|
||||
// .type_ is untyped_* and can't typeeq a concrete variant) return
|
||||
// -1 here and drop to the str/slice shape scan below — ww has no
|
||||
// type_assignable to mirror cg_variant_match's untyped-src arm.
|
||||
let r: i32 = flatvariantidx(c, tagged, rhs);
|
||||
if (r >= 0) { return r; };
|
||||
// Shape fallback: classify rhs as (str, slice, scalar/other) and
|
||||
// pick the first variant of matching shape. Cstage's type_eq
|
||||
// distinguishes a `[]u8` arm from a `u8` arm at type-build; the
|
||||
// name-only flatvariantidx pass above can't see `[]T`, so without
|
||||
// the slice axis a (u8 | []u8) widen / match collapses every
|
||||
// non-str rhs onto the leading scalar variant (task #19). Walks
|
||||
// the spread-flattened list so a `(...inner | str)` outer agrees
|
||||
// with the inner's str / slice positions.
|
||||
// pick the first variant of matching shape. Stands in for cstage's
|
||||
// type_assignable on an untyped src — the typeeq pass above can't
|
||||
// match untyped_* against a concrete variant, and the slice axis
|
||||
// keeps a (u8 | []u8) widen off the leading scalar variant (task
|
||||
// #19). Walks the spread-flattened list so a `(...inner | str)`
|
||||
// outer agrees with the inner's str / slice positions.
|
||||
let wantstr: bool = nodeisstr(c, rhs);
|
||||
let wantslice: bool = nodeisslice(c, rhs);
|
||||
let v: *node = tagged.list;
|
||||
@@ -2243,118 +2245,76 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatvariantidx — walk `tagged`'s variant list (with spread `...inner`
|
||||
// expansion) and return the flat 0-based index where `want` matches.
|
||||
// Mirrors check.c's spread flatten at type resolution: an outer
|
||||
// `(...inner | T)` has the inner's variants inlined in declaration
|
||||
// order, so the tag indices stay in sync between cstage (which
|
||||
// resolves types upfront) and wwstage (which doesn't). Returns -1 if
|
||||
// no variant matches.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, want: str) i32 = {
|
||||
// flatvariantidx — flat 0-based index of the variant whose type matches
|
||||
// the pattern node `pat`, by typeeq on the stamped tinfos. Reads the
|
||||
// pre-flattened variant chain off tinfo.params (#61a — `...inner`
|
||||
// spreads already inlined in declaration order); peels TY_NAMED then
|
||||
// gates TY_TAGGED.
|
||||
//
|
||||
// #66 Phase-N step 3 (THE FLIP, user-ruled B-full): match by
|
||||
// typeeq(p.type_, pat.type_) — nominal identity carried by the per-decl
|
||||
// TY_NAMED ptr — instead of the surface-name compare. So `type linerr =
|
||||
// !str` ≠ str and a cross-module `a.T` ≠ `b.T` are now distinguished.
|
||||
// Mirrors cstage cg_variant_match (cmd/w6c/cgen.c:451): both-NAMED →
|
||||
// ptr-id (typeeq, typ.ww:514), one-NAMED → kind mismatch → false. ww has
|
||||
// no type_assignable, so the untyped/loose arm (cg_variant_match's first
|
||||
// branch) lives in the caller's str/slice shape fallback, not here.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
if (pat == nil) { return -1; };
|
||||
let want: *tinfo = pat.type_: *tinfo;
|
||||
if (want == nil) { return -1; };
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (iv.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.str, want)) {
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
for (p != nil) {
|
||||
if (typeeq(p.type_, want)) { return idx; };
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatslicevariantidx — flat 0-based index of the first slice-shape
|
||||
// variant in `tagged` (`...inner` spread expanded). When `elem` is an
|
||||
// N_TNAME, prefer a `[]<elem.str>` variant; falls back to the first
|
||||
// slice slot if no element match is found. Cstage walks resolved
|
||||
// Type pointers and dispatches via cg_tag_for_variant / type_eq;
|
||||
// wwstage's name-keyed flatvariantidx can't see a `[]u8` variant
|
||||
// (pat.str == ""), collapsing every (scalar | []T) match arm and
|
||||
// widen-to-tagged call onto tag 0. Task #19. Returns -1 when no
|
||||
// slice variant exists.
|
||||
// flatslicevariantidx — flat 0-based index of a slice-shape variant in
|
||||
// `tagged`. Prefers the variant whose element typeeq's the pattern
|
||||
// element `elem`; falls back to the first slice-shape slot when no exact
|
||||
// element match is found (the untyped/loose arm — ww has no
|
||||
// type_assignable). Reads the flattened tinfo.params chain (#61a); peels
|
||||
// TY_NAMED then gates TY_TAGGED. The slice axis exists because a scalar-
|
||||
// vs-`[]T` distinction has no surface name to key on (task #19).
|
||||
//
|
||||
// #66 Phase-N step 3: element compare flips from surface-name to
|
||||
// typeeq(p.type_.sub, elem.type_). Mirrors cstage cg_tag_for_variant
|
||||
// over Type->params. Returns -1 when no slice variant exists.
|
||||
fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
let elemname: str;
|
||||
elemname.ptr = nil; elemname.len = 0;
|
||||
if (elem != nil) {
|
||||
if (elem.kind == nkind.N_TNAME) { elemname = elem.str; };
|
||||
};
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let want: *tinfo = nil;
|
||||
if (elem != nil) { want = elem.type_: *tinfo; };
|
||||
let fallback: i32 = -1;
|
||||
let v: *node = tagged.list;
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (isslicetype(c, iv)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (iv.kind == nkind.N_TSLICE) {
|
||||
if (iv.lhs != nil) {
|
||||
if (iv.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (isslicetype(c, v)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (v.kind == nkind.N_TSLICE) {
|
||||
if (v.lhs != nil) {
|
||||
if (v.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
for (p != nil) {
|
||||
let vt: *tinfo = p.type_;
|
||||
if (vt != nil) {
|
||||
if (typeisslice(vt)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (want != nil) {
|
||||
let su: *tinfo = vt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su != nil) {
|
||||
if (typeeq(su.sub, want)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v = v.next;
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return fallback;
|
||||
@@ -2948,15 +2908,36 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz
|
||||
let fkind: i32 = exprfloatkind(c, src);
|
||||
if (fkind != 0) {
|
||||
let fmov: str = "MOVSD";
|
||||
let fname: str = "f64";
|
||||
if (fkind == 1) { fmov = "MOVSS"; fname = "f32"; };
|
||||
if (fkind == 1) { fmov = "MOVSS"; };
|
||||
cgexpr(c, src);
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
let ftag: i32 = flatvariantidx(c, dt, fname);
|
||||
// #66 Phase-N step 3: the float arm has no pattern node to ride
|
||||
// the typeeq flatvariantidx path, so pick the variant by float
|
||||
// kind (f32 vs f64) over tinfo.params — a shape classification
|
||||
// like the slice axis, not nominal identity.
|
||||
let wantf32: bool = (fkind == 1);
|
||||
let ftag: i32 = -1;
|
||||
let fti: *tinfo = dt.type_: *tinfo;
|
||||
for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; };
|
||||
if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) {
|
||||
let fp: *tparam = fti.params;
|
||||
let fidx: i32 = 0;
|
||||
for (fp != nil) {
|
||||
let fvt: *tinfo = fp.type_;
|
||||
for (fvt != nil && fvt.kind == tykind.TY_NAMED) { fvt = fvt.under; };
|
||||
if (fvt != nil) {
|
||||
if (typeisfloat(fvt)) {
|
||||
if (typeisf32(fvt) == wantf32) { ftag = fidx; break; };
|
||||
};
|
||||
};
|
||||
fp = fp.tnext;
|
||||
fidx += 1;
|
||||
};
|
||||
}; };
|
||||
if (ftag < 0) { ftag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(ftag: i64);
|
||||
|
||||
@@ -2128,7 +2128,26 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (ms != nil) { if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||
let tn: *node = ms.decl.lhs;
|
||||
if (tn != nil) {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
// #66 Phase-N step 3: stamp e.type_ to the NOMINAL
|
||||
// per-decl NAMED, not the flattened body. `overflow{}`
|
||||
// where `type overflow = !void` must carry
|
||||
// NAMED(overflow) so the typeeq variant match
|
||||
// (cgenutil flatvariantidx) selects the overflow arm
|
||||
// instead of falling to the scalar shape fallback;
|
||||
// stamping tinfofornode(tn) gave the body (TY_VOID)
|
||||
// and lost nominal identity. Resolve through a
|
||||
// synthesized TNAME to reuse tinfofornode's TY_NAMED
|
||||
// build/cache (check.ww:1157) — the SAME NAMED ptr
|
||||
// the union variant resolved to. Mirrors cstage
|
||||
// resolving overflow{} to the overflow Type, and ww's
|
||||
// own N_CAST / N_IDENT arms which already stamp NAMED.
|
||||
// Return the body node tn unchanged: byte-id rides
|
||||
// e.type_ (cgen), while the checker's AST-level
|
||||
// assign/return checks keep their prior input.
|
||||
let tnm: *node = mktname(c, e.lhs.str);
|
||||
let nti: *tinfo = tinfofornode(c, tnm);
|
||||
if (nti != nil) { e.type_ = nti: *void; }
|
||||
else { e.type_ = tinfofornode(c, tn): *void; };
|
||||
return tn;
|
||||
};
|
||||
}; }; };
|
||||
|
||||
@@ -9225,7 +9225,26 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
if (ms != nil) { if (ms.skind == skind.SK_TYPE) { if (ms.decl != nil) {
|
||||
let tn: *node = ms.decl.lhs;
|
||||
if (tn != nil) {
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
// #66 Phase-N step 3: stamp e.type_ to the NOMINAL
|
||||
// per-decl NAMED, not the flattened body. `overflow{}`
|
||||
// where `type overflow = !void` must carry
|
||||
// NAMED(overflow) so the typeeq variant match
|
||||
// (cgenutil flatvariantidx) selects the overflow arm
|
||||
// instead of falling to the scalar shape fallback;
|
||||
// stamping tinfofornode(tn) gave the body (TY_VOID)
|
||||
// and lost nominal identity. Resolve through a
|
||||
// synthesized TNAME to reuse tinfofornode's TY_NAMED
|
||||
// build/cache (check.ww:1157) — the SAME NAMED ptr
|
||||
// the union variant resolved to. Mirrors cstage
|
||||
// resolving overflow{} to the overflow Type, and ww's
|
||||
// own N_CAST / N_IDENT arms which already stamp NAMED.
|
||||
// Return the body node tn unchanged: byte-id rides
|
||||
// e.type_ (cgen), while the checker's AST-level
|
||||
// assign/return checks keep their prior input.
|
||||
let tnm: *node = mktname(c, e.lhs.str);
|
||||
let nti: *tinfo = tinfofornode(c, tnm);
|
||||
if (nti != nil) { e.type_ = nti: *void; }
|
||||
else { e.type_ = tinfofornode(c, tn): *void; };
|
||||
return tn;
|
||||
};
|
||||
}; }; };
|
||||
@@ -12720,19 +12739,21 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
// a single canonicalization. Same shape of fix as nodeisstr.
|
||||
let resolved: *node = resolvetagged(c, tagged);
|
||||
if (resolved != nil) { tagged = resolved; };
|
||||
let wantname: str = rhstargetname(c, rhs);
|
||||
if (wantname.len > 0) {
|
||||
let r: i32 = flatvariantidx(c, tagged, wantname);
|
||||
if (r >= 0) { return r; };
|
||||
};
|
||||
// #66 Phase-N step 3: match the value's stamped type against the
|
||||
// variant types by typeeq (flatvariantidx), replacing the
|
||||
// rhstargetname surface-name compare. Untyped/loose values (whose
|
||||
// .type_ is untyped_* and can't typeeq a concrete variant) return
|
||||
// -1 here and drop to the str/slice shape scan below — ww has no
|
||||
// type_assignable to mirror cg_variant_match's untyped-src arm.
|
||||
let r: i32 = flatvariantidx(c, tagged, rhs);
|
||||
if (r >= 0) { return r; };
|
||||
// Shape fallback: classify rhs as (str, slice, scalar/other) and
|
||||
// pick the first variant of matching shape. Cstage's type_eq
|
||||
// distinguishes a `[]u8` arm from a `u8` arm at type-build; the
|
||||
// name-only flatvariantidx pass above can't see `[]T`, so without
|
||||
// the slice axis a (u8 | []u8) widen / match collapses every
|
||||
// non-str rhs onto the leading scalar variant (task #19). Walks
|
||||
// the spread-flattened list so a `(...inner | str)` outer agrees
|
||||
// with the inner's str / slice positions.
|
||||
// pick the first variant of matching shape. Stands in for cstage's
|
||||
// type_assignable on an untyped src — the typeeq pass above can't
|
||||
// match untyped_* against a concrete variant, and the slice axis
|
||||
// keeps a (u8 | []u8) widen off the leading scalar variant (task
|
||||
// #19). Walks the spread-flattened list so a `(...inner | str)`
|
||||
// outer agrees with the inner's str / slice positions.
|
||||
let wantstr: bool = nodeisstr(c, rhs);
|
||||
let wantslice: bool = nodeisslice(c, rhs);
|
||||
let v: *node = tagged.list;
|
||||
@@ -12769,118 +12790,76 @@ fn taggedvariantindex(c: *cgen, tagged: *node, rhs: *node) i32 = {
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatvariantidx — walk `tagged`'s variant list (with spread `...inner`
|
||||
// expansion) and return the flat 0-based index where `want` matches.
|
||||
// Mirrors check.c's spread flatten at type resolution: an outer
|
||||
// `(...inner | T)` has the inner's variants inlined in declaration
|
||||
// order, so the tag indices stay in sync between cstage (which
|
||||
// resolves types upfront) and wwstage (which doesn't). Returns -1 if
|
||||
// no variant matches.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, want: str) i32 = {
|
||||
// flatvariantidx — flat 0-based index of the variant whose type matches
|
||||
// the pattern node `pat`, by typeeq on the stamped tinfos. Reads the
|
||||
// pre-flattened variant chain off tinfo.params (#61a — `...inner`
|
||||
// spreads already inlined in declaration order); peels TY_NAMED then
|
||||
// gates TY_TAGGED.
|
||||
//
|
||||
// #66 Phase-N step 3 (THE FLIP, user-ruled B-full): match by
|
||||
// typeeq(p.type_, pat.type_) — nominal identity carried by the per-decl
|
||||
// TY_NAMED ptr — instead of the surface-name compare. So `type linerr =
|
||||
// !str` ≠ str and a cross-module `a.T` ≠ `b.T` are now distinguished.
|
||||
// Mirrors cstage cg_variant_match (cmd/w6c/cgen.c:451): both-NAMED →
|
||||
// ptr-id (typeeq, typ.ww:514), one-NAMED → kind mismatch → false. ww has
|
||||
// no type_assignable, so the untyped/loose arm (cg_variant_match's first
|
||||
// branch) lives in the caller's str/slice shape fallback, not here.
|
||||
fn flatvariantidx(c: *cgen, tagged: *node, pat: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
if (want.len == 0) { return -1; };
|
||||
let v: *node = tagged.list;
|
||||
if (pat == nil) { return -1; };
|
||||
let want: *tinfo = pat.type_: *tinfo;
|
||||
if (want == nil) { return -1; };
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (iv.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.str, want)) {
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (v.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.str, want)) { return idx; };
|
||||
};
|
||||
v = v.next;
|
||||
for (p != nil) {
|
||||
if (typeeq(p.type_, want)) { return idx; };
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
// flatslicevariantidx — flat 0-based index of the first slice-shape
|
||||
// variant in `tagged` (`...inner` spread expanded). When `elem` is an
|
||||
// N_TNAME, prefer a `[]<elem.str>` variant; falls back to the first
|
||||
// slice slot if no element match is found. Cstage walks resolved
|
||||
// Type pointers and dispatches via cg_tag_for_variant / type_eq;
|
||||
// wwstage's name-keyed flatvariantidx can't see a `[]u8` variant
|
||||
// (pat.str == ""), collapsing every (scalar | []T) match arm and
|
||||
// widen-to-tagged call onto tag 0. Task #19. Returns -1 when no
|
||||
// slice variant exists.
|
||||
// flatslicevariantidx — flat 0-based index of a slice-shape variant in
|
||||
// `tagged`. Prefers the variant whose element typeeq's the pattern
|
||||
// element `elem`; falls back to the first slice-shape slot when no exact
|
||||
// element match is found (the untyped/loose arm — ww has no
|
||||
// type_assignable). Reads the flattened tinfo.params chain (#61a); peels
|
||||
// TY_NAMED then gates TY_TAGGED. The slice axis exists because a scalar-
|
||||
// vs-`[]T` distinction has no surface name to key on (task #19).
|
||||
//
|
||||
// #66 Phase-N step 3: element compare flips from surface-name to
|
||||
// typeeq(p.type_.sub, elem.type_). Mirrors cstage cg_tag_for_variant
|
||||
// over Type->params. Returns -1 when no slice variant exists.
|
||||
fn flatslicevariantidx(c: *cgen, tagged: *node, elem: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
let elemname: str;
|
||||
elemname.ptr = nil; elemname.len = 0;
|
||||
if (elem != nil) {
|
||||
if (elem.kind == nkind.N_TNAME) { elemname = elem.str; };
|
||||
};
|
||||
let ti: *tinfo = tagged.type_: *tinfo;
|
||||
for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; };
|
||||
if (ti == nil) { return -1; };
|
||||
if (ti.kind != tykind.TY_TAGGED) { return -1; };
|
||||
let want: *tinfo = nil;
|
||||
if (elem != nil) { want = elem.type_: *tinfo; };
|
||||
let fallback: i32 = -1;
|
||||
let v: *node = tagged.list;
|
||||
let p: *tparam = ti.params;
|
||||
let idx: i32 = 0;
|
||||
for (v != nil) {
|
||||
let isspread: bool = (v.op == tkind.TK_ELLIPSIS);
|
||||
if (isspread) {
|
||||
let inner: *node = v;
|
||||
if (inner.kind == nkind.N_TNAME) {
|
||||
let a: *node = aliaslookup(c, inner.str);
|
||||
if (a != nil) { inner = a; };
|
||||
};
|
||||
if (inner != nil) {
|
||||
if (inner.kind == nkind.N_TTAGGED) {
|
||||
let iv: *node = inner.list;
|
||||
for (iv != nil) {
|
||||
if (isslicetype(c, iv)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (iv.kind == nkind.N_TSLICE) {
|
||||
if (iv.lhs != nil) {
|
||||
if (iv.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(iv.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
iv = iv.next;
|
||||
idx += 1;
|
||||
};
|
||||
v = v.next;
|
||||
continue;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (isslicetype(c, v)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (elemname.len > 0) {
|
||||
if (v.kind == nkind.N_TSLICE) {
|
||||
if (v.lhs != nil) {
|
||||
if (v.lhs.kind == nkind.N_TNAME) {
|
||||
if (variantnamematch(v.lhs.str, elemname)) { return idx; };
|
||||
};
|
||||
for (p != nil) {
|
||||
let vt: *tinfo = p.type_;
|
||||
if (vt != nil) {
|
||||
if (typeisslice(vt)) {
|
||||
if (fallback < 0) { fallback = idx; };
|
||||
if (want != nil) {
|
||||
let su: *tinfo = vt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su != nil) {
|
||||
if (typeeq(su.sub, want)) { return idx; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
v = v.next;
|
||||
p = p.tnext;
|
||||
idx += 1;
|
||||
};
|
||||
return fallback;
|
||||
@@ -13474,15 +13453,36 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *node, src: *node, slot_off: i32, slot_sz
|
||||
let fkind: i32 = exprfloatkind(c, src);
|
||||
if (fkind != 0) {
|
||||
let fmov: str = "MOVSD";
|
||||
let fname: str = "f64";
|
||||
if (fkind == 1) { fmov = "MOVSS"; fname = "f32"; };
|
||||
if (fkind == 1) { fmov = "MOVSS"; };
|
||||
cgexpr(c, src);
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
emitline("\tX0, ");
|
||||
emitoff((slot_off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
let ftag: i32 = flatvariantidx(c, dt, fname);
|
||||
// #66 Phase-N step 3: the float arm has no pattern node to ride
|
||||
// the typeeq flatvariantidx path, so pick the variant by float
|
||||
// kind (f32 vs f64) over tinfo.params — a shape classification
|
||||
// like the slice axis, not nominal identity.
|
||||
let wantf32: bool = (fkind == 1);
|
||||
let ftag: i32 = -1;
|
||||
let fti: *tinfo = dt.type_: *tinfo;
|
||||
for (fti != nil && fti.kind == tykind.TY_NAMED) { fti = fti.under; };
|
||||
if (fti != nil) { if (fti.kind == tykind.TY_TAGGED) {
|
||||
let fp: *tparam = fti.params;
|
||||
let fidx: i32 = 0;
|
||||
for (fp != nil) {
|
||||
let fvt: *tinfo = fp.type_;
|
||||
for (fvt != nil && fvt.kind == tykind.TY_NAMED) { fvt = fvt.under; };
|
||||
if (fvt != nil) {
|
||||
if (typeisfloat(fvt)) {
|
||||
if (typeisf32(fvt) == wantf32) { ftag = fidx; break; };
|
||||
};
|
||||
};
|
||||
fp = fp.tnext;
|
||||
fidx += 1;
|
||||
};
|
||||
}; };
|
||||
if (ftag < 0) { ftag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(ftag: i64);
|
||||
@@ -14116,17 +14116,16 @@ fn cgtagvariantidx(c: *cgen, tagged: *node, vt: *node) i32 = {
|
||||
if (tagged == nil) { return -1; };
|
||||
if (vt == nil) { return -1; };
|
||||
if (tagged.kind != nkind.N_TTAGGED) { return -1; };
|
||||
// `is []T` / `as []T` — slice-shape variant lookup routes through
|
||||
// the shape-aware helper so non-N_TNAME variant nodes (which
|
||||
// flatvariantidx's name key can't see) resolve. Task #19.
|
||||
// `is []T` / `as []T` — slice-shape lookup routes through the
|
||||
// element-aware helper, which carries the loose first-slice-shape
|
||||
// fallback (cstage type_assignable stand-in) that flatvariantidx's
|
||||
// strict typeeq below doesn't. Task #19; #66 refresh.
|
||||
if (vt.kind == nkind.N_TSLICE) {
|
||||
return flatslicevariantidx(c, tagged, vt.lhs);
|
||||
};
|
||||
let want: str;
|
||||
want.ptr = nil; want.len = 0;
|
||||
if (vt.kind == nkind.N_TNAME) { want = vt.str; };
|
||||
if (want.len == 0) { return -1; };
|
||||
return flatvariantidx(c, tagged, want);
|
||||
// #66 Phase-N step 3: match by typeeq on vt's stamped tinfo
|
||||
// (flatvariantidx), not vt's surface name.
|
||||
return flatvariantidx(c, tagged, vt);
|
||||
};
|
||||
|
||||
// cgtryprop — `e?` propagates the error variant up the stack.
|
||||
@@ -15119,7 +15118,7 @@ fn cgmatch(c: *cgen, n: *node) void = {
|
||||
if (scrutt.kind == nkind.N_TTAGGED) {
|
||||
let r: i32 = -1;
|
||||
if (pat.kind == nkind.N_TNAME) {
|
||||
r = flatvariantidx(c, scrutt, pat.str);
|
||||
r = flatvariantidx(c, scrutt, pat);
|
||||
} else { if (pat.kind == nkind.N_TSLICE) {
|
||||
// `case let s: []T =>` — pat.str is empty
|
||||
// because the variant is a composite, so
|
||||
@@ -20495,7 +20494,28 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||
// #66 Phase-N step 3: nomem propagation has no
|
||||
// pattern node, so it can't ride the typeeq
|
||||
// flatvariantidx path. cstage passes the ty_nomem
|
||||
// singleton to cg_tag_for_variant; the wwstage cgen
|
||||
// holds no tinfo singleton, so find the nomem
|
||||
// variant by its NAMED name over tinfo.params.
|
||||
let nidx: i32 = -1;
|
||||
let nti: *tinfo = nil;
|
||||
if (c.fnret != nil) { nti = c.fnret.type_: *tinfo; };
|
||||
for (nti != nil && nti.kind == tykind.TY_NAMED) { nti = nti.under; };
|
||||
if (nti != nil) { if (nti.kind == tykind.TY_TAGGED) {
|
||||
let np: *tparam = nti.params;
|
||||
let nidx2: i32 = 0;
|
||||
for (np != nil) {
|
||||
let nvt: *tinfo = np.type_;
|
||||
if (nvt != nil) {
|
||||
if (variantnamematch(nvt.name, "nomem")) { nidx = nidx2; break; };
|
||||
};
|
||||
np = np.tnext;
|
||||
nidx2 += 1;
|
||||
};
|
||||
}; };
|
||||
if (nidx < 0) { nidx = 1; };
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(nidx: i64);
|
||||
|
||||
198
test/wcc/931_variant_typekey_run.c
Normal file
198
test/wcc/931_variant_typekey_run.c
Normal file
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 931_variant_typekey_run — Class B semantic sentinel for #66
|
||||
* (Phase-N step 3, the user-ruled type-keying flip).
|
||||
*
|
||||
* Locks in the behavioral capability the corpus does NOT exercise:
|
||||
* distinguishing two co-variants that share a SHAPE (and, pre-flip,
|
||||
* a name key) by NOMINAL TYPE identity. cstage's cg_variant_match
|
||||
* (cmd/w6c/cgen.c:451) is what keeps `(str | linerr)` separable even
|
||||
* though `type linerr = !str` unwraps to str; #66 brings wwstage's
|
||||
* flatvariantidx to the same typeeq discipline (per-decl TY_NAMED ptr).
|
||||
*
|
||||
* Discriminating shape: a tagged union with the alias variant FIRST,
|
||||
* widened from a value with no surface type name (a call return).
|
||||
* Pre-#66 wwstage routed the value→tag direction through rhstargetname
|
||||
* (surface name) + a str/scalar shape fallback that picked the FIRST
|
||||
* shape-matching variant — i.e. the leading alias — so a plain `str`
|
||||
* from a call was tagged as `linerr` (idx 0) instead of `str` (idx 1).
|
||||
* A subsequent match then fired the wrong arm. cstage always type-keyed,
|
||||
* so pre-#66 this was a cstage-vs-wwstage divergence; each row exits 10
|
||||
* on pre-#66 wwstage and 0 on cstage / post-#66 wwstage. The 990-997
|
||||
* byte-id gates do not cover this because the corpus has no
|
||||
* same-shape co-variant whose name key and type key disagree.
|
||||
*
|
||||
* Pure runtime test: build through both drivers (cstage `ww`, wwstage
|
||||
* `ww_ww`) and assert each arm fires for its matching input.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src; /* single-module program — written as p.ww */
|
||||
int want;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* str axis. `(linerr | str)` with linerr = !str, alias first.
|
||||
* classify(0) widens a str returned by a call (no surface name)
|
||||
* — pre-#66 the shape fallback tags it as the leading str-shape
|
||||
* variant linerr (idx 0) and the match returns 1 not 2.
|
||||
* classify(1) widens a linerr (round-trip soundness). */
|
||||
{ "linerr_str_alias_first_callret",
|
||||
"package main;\n"
|
||||
"type linerr = !str;\n"
|
||||
"fn getstr() str = { return \"hi\"; };\n"
|
||||
"fn produce(k: i32) (linerr | str) = {\n"
|
||||
" if (k == 0) { return getstr(); };\n"
|
||||
" let e: linerr = \"boom\";\n"
|
||||
" return e;\n"
|
||||
"};\n"
|
||||
"export fn classify(k: i32) i32 = {\n"
|
||||
" match (produce(k)) {\n"
|
||||
" case let er: linerr => return 1;\n"
|
||||
" case let v: str => return 2;\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (classify(0) != 2) { return 10; };\n"
|
||||
" if (classify(1) != 1) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* scalar axis. `(ec | i32)` with ec = !i32, alias first.
|
||||
* classify(0) widens an i32 returned by a call — pre-#66 the
|
||||
* scalar shape fallback tags it as the leading scalar-shape
|
||||
* variant ec (idx 0). Confirms the flip isn't str-specific. */
|
||||
{ "errint_int_alias_first_callret",
|
||||
"package main;\n"
|
||||
"type ec = !i32;\n"
|
||||
"fn getint() i32 = { return 7; };\n"
|
||||
"fn produce(k: i32) (ec | i32) = {\n"
|
||||
" if (k == 0) { return getint(); };\n"
|
||||
" let e: ec = 9;\n"
|
||||
" return e;\n"
|
||||
"};\n"
|
||||
"export fn classify(k: i32) i32 = {\n"
|
||||
" match (produce(k)) {\n"
|
||||
" case let er: ec => return 1;\n"
|
||||
" case let v: i32 => return 2;\n"
|
||||
" };\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (classify(0) != 2) { return 10; };\n"
|
||||
" if (classify(1) != 1) { return 11; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
write_file(const char *dir, const char *name, const char *body)
|
||||
{
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/%s", dir, name);
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char tmpdir[96], cmd[2048];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/vtk_%d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
if (write_file(tmpdir, "p.ww", r->src) != 0) return -1;
|
||||
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build p.ww 2>/dev/null", tmpdir, driver);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
|
||||
runwait(cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/p", tmpdir);
|
||||
int got = runwait(outbin);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
|
||||
runwait(cmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"variant_typekey_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"variant_typekey_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"variant_typekey_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("variant_typekey_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user