wcc: #5 F1 promote type_chase_named + transitive-peel acceptance align-cs-up

Promote type_chase_named from cmd/w6c/cgen.c (static) to cmd/wcc/type.c
(exported via ww.h) and re-route every checker single-NAMED-peel through
it: check.c's ~28 inline ternaries + 3 ad-hoc loops, type.c's
assignability/untyped/borrow/opaque peels. type_eq's nominal identity
(check.c:114) and the resolve machinery guards stay untouched.

The re-route IS the acceptance align-up — cstage loud-rejected alias
shapes wwstage accepts AND runs Hare-right (F0 census, harec dealiases
at every consumer):
- #54 binop alias-vs-base: unify_arith gains the harec type_promote arm
  (ref/harec/src/check.c:1083-1105) — one-sided alias + dealias-equal
  promotes to the ALIAS side; alias-vs-alias stays rejected.
- alias-cond family: if/for/&&/||/! chase-then-bool (harec
  check.c:2141/2515/3229/3572). assert stays loud (F0 2a symmetric).
- #70 field access through 2-level alias chains (ken c3_chain3).
- assignability through the full chain (harec types.c:989-996
  dealias-both): return/init/assign legs, F0 8b idx/slice walls.
- alias-of-ptr deref (harec types.c:19-22 type_dereference).

The widening reaches cgen arms whose own single peels then misbehaved —
both classes are closed IN THIS COMMIT so no intermediate state ships a
loud->silent flip (bisect no-silent invariant):
- index family: the 8b acceptance hit ptr-load base + esz=1 (SEGV /
  prefix-luck) — idx_eff + the N_INDEX read / index-write / &base[i] /
  N_SLICE (expr + call-arg) / N_FORRANGE / aggarg_srcaddr-index /
  castsrcprim-dot / match-field base classifies chase.
- kind classifiers (ken #61-root-verify v3 find): a 2-level f64 alias
  param reached cg_isfloat's single peel and classified INT — silent
  wrong-register-class. cg_isfloat / type_isf32 / fld_isfloat /
  type_isstr / type_isslice chase. ken's v3 row is pinned with credit.

Bootstrap asm is byte-identical before/after (w6c on every
main.combined.ww cmp-equal vs a pristine 738d7f4 scratch; 989
lib_byteid pins unchanged): 2-level chains were checker-walled pre-F1,
so no previously-accepted program changes shape.

test: 944_alias_accept_run (20 rows): acceptance graduations pinned
runtime + byte-id both stages; idx/slice/range/slice-param rows cs-only
until the wwstage #60 esz family lands (F2 batch 1); cs-only
harec-parity loud pin for alias-vs-alias binop; assert stays-loud row;
ken-v3 + f64/str/slice kind rows. Mutation-checked at 738d7f4.

reviewer-F1 fold — the same invariant, outside the F0 census: this
commit ADMITS 2+-level alias slice/str/aggregate types in STRUCT FIELD
position, therefore this commit must keep them correct-or-loud. The
cgen FIELD-TYPE gates single-peeled, so the slice/str 3-word arms fell
to word0-only scalar tails — accept-and-corrupt, ww correct, every
shape loud at the pristine base. Chased (probe-proven, byte-id
graduations): single-dot field store + via-ptr twin, struct-lit fill,
chained store-walk LEAF (the #71 walk chases hops, not leaves),
chained-ptr-field store, single-dot / via-ptr / chained-walk field
reads (clobber-probed — word0 reads luck-passed on stale BX/CX). The
six unprobed sibling gates (indexed-elem store/read, ptr-chain read,
heap fill, tuple-elem read, static emit) hard-error via
fld_alias_tripwire on a 2+-level alias over an aggregate base, citing
task #73 (the family's scheduled chase); <=1-level and scalar bases
never fire — zero behavior change for any pre-#5-legal program (five
selfhost mains cmp-identical vs the pristine 738d7f4 scratch).
test: 944 +11 rows (9 K_RUN byte-id, wholeread K_RUN_CS [#60 ww half +
pre-existing 1-level read-spine divergence], #73 tripwire
K_BUILDERR_CS pin); 1-level controls per gate in /tmp/revF1.
This commit is contained in:
2026-06-05 11:52:08 +09:00
parent 738d7f481c
commit 9bd0d8bc81
6 changed files with 955 additions and 140 deletions

View File

@@ -176,25 +176,18 @@ static int nyields;
static int
cg_isfloat(Type *t)
{
if (t == NULL) return 0;
if (t->kind == TY_NAMED) t = t->under;
/* Transitive chase (#5 F1): the acceptance align opened 2-level
* float/str/slice aliases to these kind classifiers — a single
* peel mis-classed them scalar/INT (ken v3: f64-alias param read
* the wrong register class once the checker admitted it). */
t = type_chase_named(t);
if (t == NULL) return 0;
return t->kind == TY_F32 || t->kind == TY_F64
|| t->kind == TY_UNTYPED_FLOAT;
}
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the codegen silently falls
* through to a scalar shape. Mirror of wwstage's structlookupchain. */
static Type *
type_chase_named(Type *t)
{
while (t && t->kind == TY_NAMED) t = t->under;
return t;
}
/* type_chase_named lives in cmd/wcc/type.c since the #5 alias arc — the
* checker's acceptance sites share the transitive peel with cgen. */
/* cg_sret_retsize — sret classifier; defined after the tuple register-
* return helpers (tuple_rseq / tuple_eslot / fld_isfloat) it consults
@@ -211,8 +204,7 @@ node_isfloat(Node *n)
static int
type_isstr(Type *t)
{
if (t == NULL) return 0;
if (t->kind == TY_NAMED) t = t->under;
t = type_chase_named(t);
if (t == NULL) return 0;
return t->kind == TY_STR || t->kind == TY_UNTYPED_STR;
}
@@ -226,8 +218,7 @@ node_isstr(Node *n)
static int
type_isslice(Type *t)
{
if (t == NULL) return 0;
if (t->kind == TY_NAMED) t = t->under;
t = type_chase_named(t);
return t && t->kind == TY_SLICE;
}
@@ -317,8 +308,7 @@ tuple_eslot(Type *t)
static int
type_isf32(Type *t)
{
if (t == NULL) return 0;
if (t->kind == TY_NAMED) t = t->under;
t = type_chase_named(t);
return t && t->kind == TY_F32;
}
@@ -350,8 +340,7 @@ static int
fld_isfloat(Type *t, int *isf32)
{
if (isf32) *isf32 = 0;
if (t == NULL) return 0;
if (t->kind == TY_NAMED) t = t->under;
t = type_chase_named(t);
if (t == NULL) return 0;
if (t->kind == TY_F64) return 1;
if (t->kind == TY_UNTYPED_FLOAT) return 1;
@@ -444,6 +433,32 @@ fldstoreop(Type *t, int sz)
return A_MOVQ;
}
/* fld_alias_tripwire — #73 guard at the single-peel field gates this
* fold did NOT chase. The #5-F1 acceptance admits 2+-level alias field
* types everywhere; a still-NAMED type after one peel skips a gate's
* multi-word arms (slice/str 3-word, tagged widener, struct copy) for
* a word0 scalar tail — accept-and-corrupt. Loud over silent (rule 7);
* chase + per-arm probes are task #73. Fires ONLY on 2+-level chains
* over an aggregate base: <=1-level (the entire pre-#5-F1-legal set)
* and scalar/float bases (width-driven tails, depth-safe) never fire. */
static void
fld_alias_tripwire(Type *ft, const char *site)
{
Type *u = (ft && ft->kind == TY_NAMED) ? ft->under : ft;
if (u == NULL || u->kind != TY_NAMED) return;
Type *base = type_chase_named(u);
if (base == NULL) return;
switch (base->kind) {
case TY_SLICE: case TY_STR: case TY_TAGGED:
case TY_STRUCT: case TY_TUPLE: case TY_ARRAY:
fatal("%s: 2+-level alias field type '%s' at unswept "
"single-peel gate (#73)", site,
ft->name ? ft->name : "?");
default:
return;
}
}
/* castsrcprim — structural (size, unsigned) of an N_CAST's source
* expression, mirroring wwstage's exprprimresolved in
* selfhost/cmd/wcc/cgenutil.ww. The cgen-stage match has to be
@@ -502,11 +517,9 @@ castsrcprim(Node *n, int *sz, int *unsignd)
/* Real struct field only. .len / .cap / .ptr on str /
* slice / array are pseudo-fields wwstage doesn't see. */
Type *bt = n->lhs ? n->lhs->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
if (bu && bu->kind == TY_PTR) {
Type *st = bu->sub;
bu = (st && st->kind == TY_NAMED) ? st->under : st;
}
Type *bu = type_chase_named(bt);
if (bu && bu->kind == TY_PTR)
bu = type_chase_named(bu->sub);
if (bu && bu->kind == TY_STRUCT) {
t = n->type;
}
@@ -1250,9 +1263,12 @@ static Type *
idx_eff(Type *t)
{
if (t == NULL) return NULL;
Type *u = type_unwrap(t);
/* Transitive chase (#5 alias arc): the checker now admits index
* bases through 2-level alias chains (F0 8b); a single unwrap left
* eff TY_NAMED → sub NULL → esz=1 byte loads off the chain. */
Type *u = type_chase_named(t);
if (u && u->kind == TY_PTR && u->sub) {
Type *p = type_unwrap(u->sub);
Type *p = type_chase_named(u->sub);
if (p && p->kind == TY_ARRAY) return p;
}
return u;
@@ -2100,7 +2116,7 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
Node *base = src->lhs;
Node *idx = src->rhs;
Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *bu = type_chase_named(bt);
if (base && base->kind == N_IDENT && bu
&& bu->kind == TY_ARRAY) {
int esz = (bu->sub) ? (int)bu->sub->size : 1;
@@ -3032,7 +3048,11 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
break;
}
}
Type *fu = (ft && ft->kind == TY_NAMED) ? ft->under : ft;
/* Transitive chase (#5-F1 fold): a 2-level-alias slice/str
* field fell past every kind arm to the word0-only scalar
* tail — silent, reachable only via this commit's
* acceptance (reviewer-F1 s1 probe). */
Type *fu = type_chase_named(ft);
if (fu && fu->kind == TY_TAGGED) {
/* Tagged store: reload BX first (if non-BP) so the
* widener sees a valid base reg. The widener itself
@@ -4132,8 +4152,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
Node *base = opnd->lhs;
Node *idx = opnd->rhs;
Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED)
? bt->under : bt;
Type *bu = type_chase_named(bt);
Type *eff = idx_eff(bt);
int esz = (eff && eff->sub)
? (int)eff->sub->size : 1;
@@ -4703,8 +4722,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
* work cleanly.
* - else: struct local, BP-relative. */
Type *ft = f->type;
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
/* Transitive chase (#5-F1 fold): 2-level alias
* slice/str field skipped the 3-word arm — ptr
* word stored, len/cap dropped (reviewer-F1
* ix2/s2/s3 probes; c1-acceptance-reached). */
Type *fu = type_chase_named(ft);
/* Tagged-union field — full slot rewrite via the
* shared widener so every rhs shape (whole-tagged
* ident or expr with tag-remap, concrete-variant
@@ -4749,8 +4771,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
int is_global = (boff == 0 && !via_ptr
&& let_islet(base->str));
int foff = (int)f->offset;
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
? f->type->under : f->type;
Type *str_fu = type_chase_named(f->type);
/* str/slice field: str IS []u8, so both store the full
* 3-word {ptr,len,cap} that rhs cgexpr leaves in
* (AX,BX,CX) at field+0/+8/+16. Address scratch must
@@ -5025,6 +5046,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (f != NULL && (is_arr || is_sl || is_ptr)
&& off != 0) {
Type *ft = f->type;
fld_alias_tripwire(ft,
"indexed-elem field store");
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
int fsz = (int)(ft ? ft->size : 8);
@@ -5272,8 +5295,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
{ f = fl; break; }
if (f != NULL) {
Type *ft = f->type;
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
/* Transitive chase (#5-F1 fold):
* 2-level alias slice/str field
* skipped the 3-word arm
* (reviewer-F1 p1 probe). */
Type *fu = type_chase_named(ft);
int fsz = (int)(ft ? ft->size : 8);
int store_op = fldstoreop(ft, fsz);
int foff = (int)f->offset;
@@ -5576,9 +5602,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
break;
}
Type *fu = (leaf_type
&& leaf_type->kind == TY_NAMED)
? leaf_type->under : leaf_type;
/* Transitive chase (#5-F1 fold): the walk
* HOPS chase (#71) but the LEAF gate
* single-peeled — 2-level alias slice leaf
* fell to the scalar tail (reviewer-F1 s4
* probe). */
Type *fu = type_chase_named(leaf_type);
int fsz = (int)(leaf_type
? leaf_type->size : 8);
int store_op = fldstoreop(leaf_type, fsz);
@@ -5881,7 +5910,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (n->lhs->kind == N_INDEX && n->lhs->lhs && !place_slit) {
Node *base = n->lhs->lhs;
Type *bt = base->type;
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *u = type_chase_named(bt);
int is_arr = u && u->kind == TY_ARRAY;
int is_sl = u && u->kind == TY_SLICE;
int is_ptr = u && u->kind == TY_PTR;
@@ -7805,6 +7834,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
* CX=cap). Route the heap base through DX so all
* three survive — CX now holds cap, BX holds len
* (#1/Phase 3). */
fld_alias_tripwire(ftype,
"heap struct-lit field fill");
Type *fu = (ftype && ftype->kind == TY_NAMED)
? ftype->under : ftype;
if (fu && fu->kind == TY_STR) {
@@ -8642,8 +8673,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
Node *lo = args[i]->rhs;
Node *hi = args[i]->cond;
Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ?
bt->under : bt;
Type *bu = type_chase_named(bt);
/* esz from the type table for an N_IDENT base
* (#76) or an N_DOT array/slice-field base
* (#257: scale by the field's element width via
@@ -9394,7 +9424,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
* value words at +8/+16) is contiguous within the struct,
* so no spill is needed. */
Type *bt = s->lhs->type;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *bu = type_chase_named(bt);
Tfield *f = NULL;
if (bu && bu->kind == TY_STRUCT) {
for (Tfield *fl = bu->fields; fl; fl = fl->next) {
@@ -10372,9 +10402,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_AX));
goto dot_done;
}
Type *fu = (leaf_type
&& leaf_type->kind == TY_NAMED)
? leaf_type->under : leaf_type;
/* Transitive chase (#5-F1 fold):
* read twin of the store-walk
* leaf gate (reviewer-F1 r2b
* clobber probe). */
Type *fu = type_chase_named(leaf_type);
/* tagged leaf (#38a): load the box into
* the tagged cursor (AX=tag, DX=val0,
* R8=val2 before CX=val1 — base_reg may
@@ -10588,6 +10620,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
}
if (tp != NULL) {
int fsz = (int)(tp->type ? tp->type->size : 8);
fld_alias_tripwire(tp->type,
"tuple-elem read");
Type *fu = (tp->type && tp->type->kind == TY_NAMED)
? tp->type->under : tp->type;
int op = fldloadop(tp->type, fsz);
@@ -10742,8 +10776,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
* folds onto the slice arm (#1/Phase 3 collapse).
* base_reg may be CX for globals; load .cap LAST so
* the base survives the earlier reads. */
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
? f->type->under : f->type;
/* Transitive chase (#5-F1 fold): the slice half
* single-peeled while the str half (type_isstr)
* recursed — a 2-level alias slice field read
* loaded ptr only, len/cap rode stale registers
* (reviewer-F1 r1b clobber probe). */
Type *str_fu = type_chase_named(f->type);
if ((str_fu && str_fu->kind == TY_SLICE) ||
type_isstr(f->type)) {
ins2(c, A_MOVQ,
@@ -10860,8 +10898,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
* pointer, so load .len LAST — the earlier loads
* still index off the original base. str folds
* onto the slice arm (#1/Phase 3 collapse). */
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
? f->type->under : f->type;
/* Transitive chase (#5-F1 fold): via-ptr twin
* of the BP-base read gate (reviewer-F1 r3
* clobber probe). */
Type *str_fu = type_chase_named(f->type);
if ((str_fu && str_fu->kind == TY_SLICE) ||
type_isstr(f->type)) {
ins2(c, A_MOVQ,
@@ -10913,6 +10953,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (strcmp(f->name, n->str) != 0) continue;
cgexpr(c, n->lhs, locals); /* AX = inner ptr */
Type *ft = f->type;
fld_alias_tripwire(ft,
"ptr-chain field read");
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
/* tagged leaf (#38a): AX holds the *struct
@@ -11070,6 +11112,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_AX));
int foff = (int)f->offset;
Type *ft = f->type;
fld_alias_tripwire(ft,
"indexed-elem field read");
Type *fu = (ft && ft->kind == TY_NAMED)
? ft->under : ft;
/* #270-1a: an `[N]T`-typed field of an
@@ -11232,7 +11276,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
&& let_islet(n->lhs->str)) {
bt = let_var_type(n->lhs->str);
}
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *u = type_chase_named(bt);
Type *eff = idx_eff(bt);
int esz = 1;
if (eff && eff->sub) esz = (int)eff->sub->size;
@@ -11419,7 +11463,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
Node *lo = n->rhs;
Node *hi = n->cond;
Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
/* Transitive chase (#5 alias arc) — see the N_INDEX twin. */
Type *bu = type_chase_named(bt);
/* esz from the type table for an N_IDENT base (#76) or an
* N_DOT array/slice-field base (#252: a struct-field slice
* `s.obuf[lo:hi]` must scale by the field's element width, not
@@ -13173,10 +13218,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* or pulls each tuple field into its respective local. */
Node *slc = n->lhs;
Type *st = slc ? slc->type : NULL;
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
/* Transitive chase (#5 alias arc): the checker admits range
* bases through 2-level alias chains (F0 8b); the single peel
* left u TY_NAMED → esz=1 + the non-array base arm (MOVQ of
* array words as a pointer — SEGV). */
Type *u = type_chase_named(st);
int esz = (u && u->sub) ? (int)u->sub->size : 1;
Type *etu = (u && u->sub && u->sub->kind == TY_NAMED)
? u->sub->under : (u ? u->sub : NULL);
Type *etu = type_chase_named(u ? u->sub : NULL);
int destruct = (n->list != NULL);
/* allocate temp slots: _i (8B), _len (8B). #70: a NON-IDENT
@@ -14191,6 +14239,7 @@ emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base)
}
Node *vr = v;
while (vr != NULL && vr->kind == N_CAST) vr = vr->lhs;
fld_alias_tripwire(f->type, "static struct-lit emit");
Type *fu = (f->type && f->type->kind == TY_NAMED)
? f->type->under : f->type;
if (fu && fu->kind == TY_STRUCT) {

View File

@@ -344,7 +344,7 @@ addrfn_ptr_matches(Type *ptr, Type *fnty)
{
if (ptr == NULL || ptr->kind != TY_PTR) return 0;
Type *ref = ptr->sub;
if (ref && ref->kind == TY_NAMED) ref = ref->under;
ref = type_chase_named(ref);
if (ref == NULL || ref->kind != TY_FN) return 0;
return type_eq(ref, fnty);
}
@@ -377,7 +377,7 @@ assignable_addrfn(Checker *c, Type *dst, Node *rhs)
if (s == NULL || s->kind != SK_FN) return 0;
Type *fnty = s->type;
if (fnty == NULL || fnty->kind != TY_FN) return 0;
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *du = type_chase_named(dst);
if (du == NULL) return 0;
if (du->kind == TY_PTR) return addrfn_ptr_matches(du, fnty);
if (du->kind == TY_TAGGED) {
@@ -408,7 +408,7 @@ static int
arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
{
if (rhs == NULL || rhs->kind != N_ARRLIT) return 0;
Type *u = (dt && dt->kind == TY_NAMED) ? dt->under : dt;
Type *u = type_chase_named(dt);
/* #25: a SLICE target is admitted via the same per-element coercion
* as the array path — the #258 borrow demands an exact element
* type_eq, which an arrlit's self-stamped [N]<default> can't meet for
@@ -417,7 +417,7 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
if (u == NULL || (u->kind != TY_ARRAY && u->kind != TY_SLICE))
return 0;
Type *et = u->sub;
Type *eu = (et && et->kind == TY_NAMED) ? et->under : et;
Type *eu = type_chase_named(et);
u64 count = 0;
for (Node *e = rhs->list; e; e = e->next) {
if (e->kind == N_FIELD && e->str
@@ -705,8 +705,7 @@ resolve_type(Checker *c, Node *n)
* the checker-says-8/cgen-does-16 split behind the
* packed-tuple miscompile family (#32/#33/#48). */
if (tp->type) {
Type *eu = tp->type->kind == TY_NAMED
? tp->type->under : tp->type;
Type *eu = type_chase_named(tp->type);
if (eu && (eu->kind == TY_STR
|| eu->kind == TY_SLICE
|| eu->kind == TY_TAGGED))
@@ -753,8 +752,7 @@ resolve_type(Checker *c, Node *n)
* (possibly NAMED) inner tagged union into the
* enclosing union — matches Hare's parse-time
* unwrap flag on each tagged_type entry. */
Type *vu = spread && vt && vt->kind == TY_NAMED
? vt->under : vt;
Type *vu = spread ? type_chase_named(vt) : vt;
if (vu && vu->kind == TY_TAGGED &&
(spread || vt->kind == TY_TAGGED)) {
for (Tparam *src = vu->params; src; src = src->next) {
@@ -885,7 +883,7 @@ resolve_type(Checker *c, Node *n)
/* embed (anonymous struct or bare-name): the inner type
* must be a struct; its fields are promoted to the outer
* scope with offsets shifted by the embed base. */
Type *inner = (ft && ft->kind == TY_NAMED) ? ft->under : ft;
Type *inner = type_chase_named(ft);
if (inner == NULL || inner->kind != TY_STRUCT) {
err(c, f->pos, "embedded type must be a struct");
off += ft ? ft->size : 0;
@@ -982,6 +980,27 @@ unify_arith(Checker *c, Pos p, Type *a, Type *b)
if (type_isuntyped(a) && type_assignable(b, a)) return b;
if (type_isuntyped(b) && type_assignable(a, b)) return a;
if (type_eq(a, b)) return a;
/* One-sided alias vs its (transitive) base promotes to the ALIAS
* side; alias vs DIFFERENT alias stays rejected even when the
* bases agree. Mirrors harec type_promote (ref/harec/src/check.c:
* 1083-1105: ALIAS+ALIAS → NULL, then dealias-equal → the alias
* operand). wwstage accepts these shapes and runs them on the
* chased width (F0 m1_binop/m1_binop2, ww runtime-correct).
*
* ERROR axis guard: `type invalid = !i32` must NOT promote against
* a plain i32 — type_eq ignores iserror (primitives compare by
* kind), but harec interns flags into DISTINCT types, so flagged-
* vs-unflagged never reaches type_promote's dealias-equal arm. The
* #246 loud (strconv.invalid != i32, pinned by 949_errtype_compare
* on BOTH stages) rides this reject. */
{
Type *da = type_chase_named(a);
Type *db = type_chase_named(b);
if (!(a->kind == TY_NAMED && b->kind == TY_NAMED) &&
da && db && da->iserror == db->iserror &&
type_eq(da, db))
return (a->kind == TY_NAMED) ? a : b;
}
return err(c, p, "operands have differing types %s and %s",
type_name(c->a, a), type_name(c->a, b));
}
@@ -1017,9 +1036,7 @@ coerce_floatlit(Node *n, Type *target)
return;
/* Chase the full alias chain (wwstage resolvealias does the same), so
* a doubly-aliased f32 target stamps in both stages or neither. */
Type *u = target;
while (u && u->kind == TY_NAMED)
u = u->under;
Type *u = type_chase_named(target);
if (u == NULL || u->kind != TY_F32)
return;
if (n->kind == N_FLOATLIT && n->type == ty_untyped_float)
@@ -1054,7 +1071,7 @@ static int
reject_arrlit_borrow(Checker *c, Type *dst, Node *expr)
{
if (expr == NULL || expr->kind != N_ARRLIT) return 0;
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
Type *du = type_chase_named(dst);
if (du == NULL || du->kind != TY_SLICE) return 0;
err(c, expr->pos, "array literal cannot borrow as a slice here; "
"bind it to a `let` first");
@@ -1066,9 +1083,8 @@ desugar_arrayslice(Checker *c, Type *dst, Node *expr)
{
if (dst == NULL || expr == NULL || expr->type == NULL)
return;
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (expr->type->kind == TY_NAMED) ? expr->type->under
: expr->type;
Type *du = type_chase_named(dst);
Type *su = type_chase_named(expr->type);
if (du == NULL || su == NULL ||
du->kind != TY_SLICE || su->kind != TY_ARRAY)
return;
@@ -1119,12 +1135,18 @@ cbinop(Checker *c, Node *n)
err(c, n->pos, "ordered comparison on non-numeric");
(void)unify_arith(c, n->pos, l, r);
return ty_bool;
case TK_AND: case TK_OR:
if (!(l == ty_bool || l == ty_untyped_bool || l == ty_err))
case TK_AND: case TK_OR: {
/* Bool-ness through the alias chain — harec dealiases at the
* logical-binop consumer (ref/harec/src/check.c:3229); ww
* accepts + runs the alias-bool operand (F0 m2_andor). */
Type *lu = type_chase_named(l);
Type *ru = type_chase_named(r);
if (!(lu == ty_bool || lu == ty_untyped_bool || l == ty_err))
err(c, n->pos, "left of %s is not bool", tokname(n->op));
if (!(r == ty_bool || r == ty_untyped_bool || r == ty_err))
if (!(ru == ty_bool || ru == ty_untyped_bool || r == ty_err))
err(c, n->pos, "right of %s is not bool", tokname(n->op));
return ty_bool;
}
default:
return err(c, n->pos, "unsupported binary op %s", tokname(n->op));
}
@@ -1139,20 +1161,28 @@ cunop(Checker *c, Node *n)
if (!type_isnum(t))
return err(c, n->pos, "%s on non-numeric", tokname(n->op));
return t;
case TK_NOT:
if (!(t == ty_bool || t == ty_untyped_bool || t == ty_err))
case TK_NOT: {
/* harec dealiases at the `!` consumer (ref/harec/src/check.c:
* 3572); ww accepts + runs the alias-bool operand (F0 m2_bang). */
Type *u = type_chase_named(t);
if (!(u == ty_bool || u == ty_untyped_bool || t == ty_err))
err(c, n->pos, "! on non-bool");
return ty_bool;
}
case TK_TILDE:
if (!type_isint(t))
return err(c, n->pos, "~ on non-integer");
return t;
case TK_STAR: /* deref */
case TK_STAR: { /* deref */
if (t == ty_err) return ty_err;
if (t->kind != TY_PTR)
/* harec type_dereference dealiases the operand (ref/harec/src/
* types.c:19-22); wwstage unoptype TK_STAR resolvealias-chases. */
Type *u = type_chase_named(t);
if (u == NULL || u->kind != TY_PTR)
return err(c, n->pos, "cannot deref non-pointer %s",
type_name(c->a, t));
return t->sub;
return u->sub;
}
case TK_AMP: /* address-of */
/* Slice/str pseudo-fields .len/.cap surface as i32 but live
* in 8B-aligned slots in the header (ptr@0, len@8, cap@16).
@@ -1165,9 +1195,9 @@ cunop(Checker *c, Node *n)
(strcmp(n->lhs->str, "len") == 0 ||
strcmp(n->lhs->str, "cap") == 0)) {
Type *bt = n->lhs->lhs->type;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *bu = type_chase_named(bt);
if (bu && bu->kind == TY_PTR) bu = bu->sub;
if (bu && bu->kind == TY_NAMED) bu = bu->under;
bu = type_chase_named(bu);
if (bu && (bu->kind == TY_SLICE || bu->kind == TY_STR))
return type_ptr(c->a, ty_i64);
}
@@ -1269,8 +1299,7 @@ cexpr(Checker *c, Node *n)
* (named) enum type itself, so bitwise ops between
* members yield the same enum type via type_eq. */
if (ms && ms->kind == SK_TYPE && ms->type) {
Type *u = (ms->type->kind == TY_NAMED)
? ms->type->under : ms->type;
Type *u = type_chase_named(ms->type);
if (u && u->kind == TY_ENUM) {
for (Tfield *f = u->fields; f; f = f->next) {
if (f->name && n->str &&
@@ -1295,9 +1324,9 @@ cexpr(Checker *c, Node *n)
}
Type *base = cexpr(c, n->lhs);
if (base == NULL || base == ty_err) return n->type = ty_err;
Type *u = (base->kind == TY_NAMED) ? base->under : base;
Type *u = type_chase_named(base);
if (u && u->kind == TY_PTR) u = u->sub;
if (u && u->kind == TY_NAMED) u = u->under;
u = type_chase_named(u);
/* Enum member access via a qualified base, e.g. `os.whence.CUR`.
* The inner N_DOT resolved through SK_USE → the SK_TYPE sym's
* named type. Fold the outer access to the member literal. */
@@ -1364,7 +1393,7 @@ cexpr(Checker *c, Node *n)
if (idx != ty_err && !type_isint(idx))
err(c, n->pos, "index must be integer");
if (base == ty_err) return n->type = ty_err;
Type *u = (base->kind == TY_NAMED) ? base->under : base;
Type *u = type_chase_named(base);
if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) {
/* #108(b): indexing needs the element size; `[]opaque`
* is a legal (sized) header but its element is unsized.
@@ -1447,9 +1476,9 @@ cexpr(Checker *c, Node *n)
n->list->kind == N_DOT) {
Node *dot = n->list;
Type *bt = cexpr(c, dot->lhs);
Type *u = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
Type *u = type_chase_named(bt);
if (u && u->kind == TY_PTR) u = u->sub;
if (u && u->kind == TY_NAMED) u = u->under;
u = type_chase_named(u);
u64 off = 0;
int found = 0;
if (u && u->kind == TY_STRUCT) {
@@ -1544,8 +1573,7 @@ cexpr(Checker *c, Node *n)
err(c, n->pos, "delete: operand must be an indexing or slicing expression");
else {
Type *bt = d->lhs ? d->lhs->type : NULL;
while (bt && bt->kind == TY_NAMED)
bt = bt->under;
bt = type_chase_named(bt);
/* harec check.c:2024 wording; a
* fixed-size [N]T base and a str
* base land here. */
@@ -1583,8 +1611,7 @@ cexpr(Checker *c, Node *n)
err(c, n->pos, "insert: operand must be an indexing expression xs[i]");
else {
Type *bt = d->lhs ? d->lhs->type : NULL;
while (bt && bt->kind == TY_NAMED)
bt = bt->under;
bt = type_chase_named(bt);
/* harec check.c:807 wording; a
* fixed-size [N]T base lands here. */
if (bt == NULL || bt->kind != TY_SLICE)
@@ -1696,7 +1723,7 @@ cexpr(Checker *c, Node *n)
(void)cexpr(c, a);
return n->type = ty_err;
}
Type *u = (ft->kind == TY_NAMED) ? ft->under : ft;
Type *u = type_chase_named(ft);
if (u == NULL || u->kind != TY_FN)
return n->type = err(c, n->pos, "calling non-function %s",
type_name(c->a, ft));
@@ -1794,7 +1821,7 @@ cexpr(Checker *c, Node *n)
} else {
t = resolve_type(c, n->lhs);
}
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *u = type_chase_named(t);
for (Node *f = n->list; f; f = f->next) {
Type *vt = cexpr(c, f->lhs);
if (u && u->kind == TY_STRUCT) {
@@ -1837,7 +1864,7 @@ cexpr(Checker *c, Node *n)
Type *base = cexpr(c, n->lhs);
if (n->rhs) (void)cexpr(c, n->rhs);
if (n->cond) (void)cexpr(c, n->cond);
Type *u = (base && base->kind == TY_NAMED) ? base->under : base;
Type *u = type_chase_named(base);
if (u && u->kind == TY_ARRAY)
return n->type = type_slice(c->a, u->sub);
if (u && u->kind == TY_SLICE)
@@ -1856,14 +1883,14 @@ cexpr(Checker *c, Node *n)
}
case N_RECV: {
Type *t = cexpr(c, n->lhs);
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *u = type_chase_named(t);
if (u && u->kind == TY_CHAN) return n->type = u->sub;
return n->type = err(c, n->pos, "<- expects chan, got %s",
type_name(c->a, t));
}
case N_MATCH: {
Type *st = cexpr(c, n->lhs);
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
Type *u = type_chase_named(st);
if (u == NULL || u->kind != TY_TAGGED) {
return n->type = err(c, n->pos,
"match on non-tagged-union %s", type_name(c->a, st));
@@ -1968,13 +1995,13 @@ cexpr(Checker *c, Node *n)
* whether the expression returns bool (is) or the variant
* itself (as). */
if (n->rhs) n->rhs->type = vt;
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *u = type_chase_named(t);
/* Enum ↔ integer cast: `enumval as intT` or `int as enumT`.
* Reinterpret-only — the storage shape is already integer, so
* cgen treats the cast as a no-op (the value lives in the same
* register). The `is` form is rejected; enums aren't sums. */
Type *uu = u;
Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt;
Type *vu = type_chase_named(vt);
int lhs_enum = uu && uu->kind == TY_ENUM;
int rhs_enum = vu && vu->kind == TY_ENUM;
if (n->kind == N_TYPEASSERT && (lhs_enum || rhs_enum) &&
@@ -2012,7 +2039,7 @@ cexpr(Checker *c, Node *n)
}
case N_TRYPROP: case N_TRYUNW: {
Type *t = cexpr(c, n->lhs);
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *u = type_chase_named(t);
if (u == NULL || u->kind != TY_TAGGED) {
return n->type = err(c, n->pos,
"%s on non-tagged-union %s",
@@ -2049,7 +2076,7 @@ cexpr(Checker *c, Node *n)
}
if (n->kind == N_TRYPROP && has_errors) {
Type *r = c->ret;
Type *ru = (r && r->kind == TY_NAMED) ? r->under : r;
Type *ru = type_chase_named(r);
if (ru == NULL || ru->kind != TY_TAGGED) {
err(c, n->pos,
"?: enclosing function must return a tagged "
@@ -2097,8 +2124,7 @@ cexpr(Checker *c, Node *n)
tp->type = cexpr(c, e);
Type *ed = type_default(tp->type);
if (ed && ed->align > al) al = ed->align;
Type *eu = (ed && ed->kind == TY_NAMED)
? ed->under : ed;
Type *eu = type_chase_named(ed);
if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE
|| eu->kind == TY_TAGGED))
sz += (eu->size + 7) & ~(u64)7;
@@ -2154,9 +2180,7 @@ clet(Checker *c, Node *n)
* init) can't infer its length — that is a loud error, never a
* silent zero-length array (rule 7, #7). */
if (declared && declared->kind == TY_ARRAY && declared->alen == 0) {
Type *iu = initt
? ((initt->kind == TY_NAMED) ? initt->under : initt)
: NULL;
Type *iu = type_chase_named(initt);
if (iu && iu->kind == TY_ARRAY)
declared = type_array(c->a, declared->sub, iu->alen);
else
@@ -2195,8 +2219,7 @@ clet(Checker *c, Node *n)
}
}
if (has_arr_repeat && declared) {
Type *du = (declared->kind == TY_NAMED) ? declared->under
: declared;
Type *du = type_chase_named(declared);
if (du && du->kind == TY_ARRAY)
has_arr_repeat = 0;
}
@@ -2303,7 +2326,11 @@ cstmt(Checker *c, Node *n)
}
case N_IF: {
Type *ct = cexpr(c, n->cond);
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
/* harec dealiases at the if-cond consumer (ref/harec/src/
* check.c:2141); ww accepts + runs alias-bool (F0 m2_if).
* assert stays UN-chased — both stages loud there (F0 2a). */
Type *cu = type_chase_named(ct);
if (ct != ty_err && cu != ty_bool && cu != ty_untyped_bool)
err(c, n->pos, "if condition must be bool, got %s",
type_name(c->a, ct));
cstmt(c, n->body);
@@ -2315,14 +2342,14 @@ cstmt(Checker *c, Node *n)
c->cur = newscope(c->a, saved);
c->loops++;
Type *st = cexpr(c, n->lhs);
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
Type *u = type_chase_named(st);
Type *elem = NULL;
if (u && (u->kind == TY_SLICE || u->kind == TY_ARRAY)) elem = u->sub;
else if (u && u->kind == TY_STR) elem = ty_u8;
else err(c, n->pos, "for-range needs slice/array/str");
if (n->list != NULL) {
/* tuple destructure: each name binds to a tuple field */
Type *etu = (elem && elem->kind == TY_NAMED) ? elem->under : elem;
Type *etu = type_chase_named(elem);
Tparam *tp = (etu && etu->kind == TY_TUPLE) ? etu->params : NULL;
for (Node *nm = n->list; nm; nm = nm->next) {
Type *ft = tp ? tp->type : ty_err;
@@ -2355,7 +2382,10 @@ cstmt(Checker *c, Node *n)
if (n->lhs) cstmt(c, n->lhs); /* init may be a let or expr */
if (n->cond) {
Type *ct = cexpr(c, n->cond);
if (ct != ty_err && ct != ty_bool && ct != ty_untyped_bool)
/* harec dealiases at the loop-cond consumer (ref/
* harec/src/check.c:2515); F0 m2_while. */
Type *cu = type_chase_named(ct);
if (ct != ty_err && cu != ty_bool && cu != ty_untyped_bool)
err(c, n->pos, "for condition must be bool, got %s",
type_name(c->a, ct));
}
@@ -2855,9 +2885,7 @@ check_file(Checker *c, Node *file)
* the right `.len`. */
if (d->type && d->type->kind == TY_ARRAY
&& d->type->alen == 0) {
Type *iu = rt
? ((rt->kind == TY_NAMED) ? rt->under : rt)
: NULL;
Type *iu = type_chase_named(rt);
if (iu && iu->kind == TY_ARRAY) {
d->type = type_array(c->a,
d->type->sub, iu->alen);

View File

@@ -147,6 +147,22 @@ type_named(Arena *a, const char *name, Type *under)
return t;
}
/* type_chase_named — walk the TY_NAMED.under chain to the deepest non-
* named type. Chain-of-aliases (#22): `type b = a; type a = struct;`
* stacks two TY_NAMED layers — a single peel leaves `t` pointing at
* the inner alias (still TY_NAMED), so kind-gated arms (TY_STRUCT,
* TY_SLICE, TY_TAGGED, TY_PTR) miss and the consumer silently falls
* through to a scalar shape. Mirror of wwstage's structlookupchain /
* resolvealias and harec's type_dealias (ref/harec/src/types.c:53).
* Promoted from cmd/w6c/cgen.c for the #5 alias arc so the checker's
* acceptance sites and the cgen classify sites share one peel. */
Type *
type_chase_named(Type *t)
{
while (t && t->kind == TY_NAMED) t = t->under;
return t;
}
int
type_isint(Type *t)
{
@@ -297,11 +313,11 @@ type_assignable(Type *dst, Type *src)
* src appears as a variant of dst (Hare-style subset). Tag
* remap at the use site handles different variant indices.
* Checked before the untyped branch so untyped literals flow
* through to a variant's typed slot. Unwraps a NAMED alias on
* either side so `type result = (T|E);` accepts variants too. */
* through to a variant's typed slot. Chases the NAMED alias chain
* on either side so `type result = (T|E);` accepts variants too. */
{
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : src;
Type *du = type_chase_named(dst);
Type *su = type_chase_named(src);
if (du && du->kind == TY_TAGGED &&
!(su && su->kind == TY_TAGGED)) {
/* #199(α): direct variant only — no transitive drill into
@@ -314,8 +330,7 @@ type_assignable(Type *dst, Type *src)
* `is`/`as`'s non-recursive variant lookup. Callers compose
* `let inner: Wrapper = sub; let r: parent = inner;`. */
for (Tparam *p = du->params; p; p = p->next) {
Type *pu = (p->type && p->type->kind == TY_NAMED)
? p->type->under : p->type;
Type *pu = type_chase_named(p->type);
if (pu && pu->kind == TY_TAGGED) {
if (type_eq(p->type, src)) return 1;
continue;
@@ -335,8 +350,7 @@ type_assignable(Type *dst, Type *src)
* the subset loop below would reject. SSoT with `is`/`as`
* variant lookup (#198 family). */
for (Tparam *dp = du->params; dp; dp = dp->next) {
Type *pu = (dp->type && dp->type->kind == TY_NAMED)
? dp->type->under : dp->type;
Type *pu = type_chase_named(dp->type);
if (pu && pu->kind == TY_TAGGED &&
type_eq(dp->type, src)) return 1;
}
@@ -354,15 +368,15 @@ type_assignable(Type *dst, Type *src)
/* Untyped → typed: only if the typed kind can hold the value. */
if (type_isuntyped(src)) {
Type *du = type_chase_named(dst);
if (src->kind == TY_UNTYPED_INT && type_isnum(dst)) return 1;
if (src->kind == TY_UNTYPED_FLOAT && type_isfloat(dst)) return 1;
if (src->kind == TY_UNTYPED_STR && (dst->kind == TY_STR ||
(dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_STR))) return 1;
if (src->kind == TY_UNTYPED_STR && du && du->kind == TY_STR)
return 1;
if (src->kind == TY_UNTYPED_RUNE && (type_isint(dst) || dst->kind == TY_RUNE)) return 1;
if (src->kind == TY_UNTYPED_BOOL && (dst->kind == TY_BOOL ||
(dst->kind == TY_NAMED && dst->under && dst->under->kind == TY_BOOL))) return 1;
if (src->kind == TY_UNTYPED_BOOL && du && du->kind == TY_BOOL)
return 1;
if (src->kind == TY_UNTYPED_NIL) {
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
if (du && (du->kind == TY_PTR || du->kind == TY_SLICE ||
du->kind == TY_CHAN || du->kind == TY_FN))
return 1;
@@ -370,11 +384,16 @@ type_assignable(Type *dst, Type *src)
return 0;
}
/* Named on either side: compare to the underlying. NAMED is a
* distinct type from its under; but assignment from under to
* named (and vice-versa) is allowed in this minimal checker. */
if (dst->kind == TY_NAMED && type_eq(dst->under, src)) return 1;
if (src->kind == TY_NAMED && type_eq(dst, src->under)) return 1;
/* Named on either side: assignable through the FULL alias chain.
* harec type_is_assignable dealiases both sides whenever the dst
* is not tagged (ref/harec/src/types.c:993-996; the tagged dst
* returned above) — so base↔alias and alias↔alias-of-same-base
* all flow. The pre-#5 single peel rejected any 2-level chain
* (`return x*2` from a myint2 local louded at the int return). */
if (dst->kind == TY_NAMED || src->kind == TY_NAMED) {
if (type_eq(type_chase_named(dst), type_chase_named(src)))
return 1;
}
/* Tuple-to-tuple: element-wise assignable. */
if (dst->kind == TY_TUPLE && src->kind == TY_TUPLE) {
@@ -397,8 +416,8 @@ type_assignable(Type *dst, Type *src)
* {.ptr=&arr[0], .len=N, .cap=N} is byte-identical across stages.
* Element types must match exactly — no element decay. */
{
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : src;
Type *du = type_chase_named(dst);
Type *su = type_chase_named(src);
if (du && su && du->kind == TY_SLICE && su->kind == TY_ARRAY &&
su->alen != SIZE_UNDEFINED && type_eq(du->sub, su->sub))
return 1;
@@ -421,8 +440,8 @@ type_assignable(Type *dst, Type *src)
* Both rules fire only when the destination element is opaque, so
* they are inert on the opaque-free selfhost corpus. */
{
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
Type *su = (src->kind == TY_NAMED) ? src->under : src;
Type *du = type_chase_named(dst);
Type *su = type_chase_named(src);
if (du && su && du->kind == su->kind &&
(du->kind == TY_PTR || du->kind == TY_SLICE) &&
du->sub && du->sub->kind == TY_OPAQUE)

View File

@@ -472,6 +472,7 @@ Type *type_chan(Arena*, Type *sub);
Type *type_named(Arena*, const char *name, Type *under);
const char *type_name(Arena*, Type*); /* arena'd debug string */
int type_eq(Type *a, Type *b); /* structural equality */
Type *type_chase_named(Type *t); /* transitive TY_NAMED peel */
int type_isint(Type *t);
int type_isfloat(Type *t);
int type_isnum(Type *t);