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

@@ -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)