selfhost/cmd/wcc/check: build per-decl TY_NAMED in tinfofornode (#64, Phase-N step 2)

tinfofornode's TNAME arm collapsed aliases to their underlying tinfo; flip
it to build a per-decl TY_NAMED wrapper cached on sym.type_, so every TNAME
resolving to the same decl yields one tinfo pointer -- ptr-identity =
nominal identity. Mirrors cstage's two-phase type_named (cmd/wcc/check.c:
1900-1929, resolve_typename :60-88): create the NAMED, pre-bind sym.type_
BEFORE resolving under (self-ref cycle-break, e.g. `type node = struct
{next: *node}`), then patch under + copy size/align/slotsize off the
immediate body. CHAINS not flatten (`type a=b` gives under=NAMED(b)),
matching resolve_typename returning the inner NAMED.

aliassym factored out of resolvealias for the one-level decl lookup;
resolvealias delegates and is behaviorally identical.

Semantically INERT until typeeq consumes nominal identity (step 3, #16) --
live type equality today is the AST-keyed typeeqast, and typeeq has no live
callers. The #63 structural-walker peels + cstage-mirrored single-if peels
keep all tinfo.kind sites correct with NAMED flowing -- byte-id 990-997
unchanged (133/133, independently re-confirmed on a quiescent tree).
This commit is contained in:
2026-05-23 19:33:04 +09:00
parent ae6a59a355
commit 3866ea24f5
3 changed files with 333 additions and 228 deletions

View File

@@ -7602,15 +7602,17 @@ fn unwrapbang(n: *node) *node = {
return n; return n;
}; };
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return // aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// the typedecl's body (possibly recursively). Pass-through for any // symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a // unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// name we can't resolve. // out of resolvealias so tinfofornode's TY_NAMED build can reach the
fn resolvealias(c: *checker, n: *node) *node = { // decl sym (nominal identity = sym.type_ ptr-identity) instead of
let cur: *node = n; // flattening to the underlying. Mirrors cstage resolve_typename
for (cur != nil) { // (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
if (cur.kind != nkind.N_TNAME) { return cur; }; fn aliassym(c: *checker, n: *node) *sym = {
let nm: str = cur.str; if (n == nil) { return nil; };
if (n.kind != nkind.N_TNAME) { return nil; };
let nm: str = n.str;
// #51: pkg.alias type refs land here as a single TNAME whose // #51: pkg.alias type refs land here as a single TNAME whose
// str is the joined form (lib/ww/parse/parse.ww:258-265 in // str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in // parsetype). Split on the rightmost '.' and bind the leaf in
@@ -7665,8 +7667,21 @@ fn resolvealias(c: *checker, n: *node) *node = {
}; };
}; };
}; };
if (s == nil) { return nil; };
if (s.skind != skind.SK_TYPE) { return nil; };
return s;
};
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return
// the typedecl's body (possibly recursively). Pass-through for any
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a
// name we can't resolve.
fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; }; if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil; let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; }; if (s.decl != nil) { body = s.decl.lhs; };
if (body == nil) { return cur; }; if (body == nil) { return cur; };
@@ -8235,27 +8250,47 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; };
if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) { if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse. // #64 Phase-N step 2 (THE FLIP): build a per-decl
// Mirrors astsize's TNAME fallback so the helpers stay in // TY_NAMED wrapper instead of collapsing the alias to
// lockstep until A.2 collapses each cgen size-walker onto // its underlying. sym.type_ caches the wrapper so every
// tinfo.size directly. // TNAME resolving to the same decl yields the SAME tinfo
// // pointer — ptr-identity IS nominal identity (the whole
// #61 A.4: bind the resolved body too so future // point; typeeq is the only consumer, wired in step 3).
// tinfofornode calls on either the TNAME or its target // CHAINS, not flatten: under is the IMMEDIATE body's
// short-circuit on the cache hit instead of re-walking // tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/ // — mirrors cstage's two-phase type_named
// TTAGGED cycle-break pattern (a self-referential // (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// struct field's *T → TNAME → body would otherwise // pass2 patches under/size off resolve_type(d->lhs),
// re-enter the same chain). // where resolve_typename returns the inner NAMED.
let body: *node = resolvealias(c, n); let s: *sym = aliassym(c, n);
if (body != nil && body != n) { if (s != nil) {
let cached2: *tinfo = tinfocachelookup(c.tc, body); if (s.type_ != nil) {
if (cached2 != nil) { r = s.type_;
r = cached2;
} else { } else {
r = tinfofornode(c, body); let body: *node = nil;
if (r != nil) { if (s.decl != nil) {
tinfocachebind(c.tc, body, r); body = unwrapbang(s.decl.lhs);
};
if (body != nil) {
// PRE-BIND before resolving under: a
// self-referential field (`type node =
// struct {next: *node}`) re-finds this
// NAMED via sym.type_ instead of re-
// entering the chain. Mirrors cstage
// pass1's sym->type install
// (check.c:1909/1917) ahead of pass2's
// under patch, and A.2's TSTRUCT/TFN/
// TTAGGED tinfocachebind cycle-break.
let named: *tinfo = typenamed(s.name, nil);
s.type_ = named;
let under: *tinfo = tinfofornode(c, body);
named.under = under;
if (under != nil) {
named.size = under.size;
named.align = under.align;
named.slotsize = under.slotsize;
};
r = named;
}; };
}; };
}; };

View File

@@ -505,15 +505,17 @@ fn unwrapbang(n: *node) *node = {
return n; return n;
}; };
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return // aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// the typedecl's body (possibly recursively). Pass-through for any // symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a // unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// name we can't resolve. // out of resolvealias so tinfofornode's TY_NAMED build can reach the
fn resolvealias(c: *checker, n: *node) *node = { // decl sym (nominal identity = sym.type_ ptr-identity) instead of
let cur: *node = n; // flattening to the underlying. Mirrors cstage resolve_typename
for (cur != nil) { // (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
if (cur.kind != nkind.N_TNAME) { return cur; }; fn aliassym(c: *checker, n: *node) *sym = {
let nm: str = cur.str; if (n == nil) { return nil; };
if (n.kind != nkind.N_TNAME) { return nil; };
let nm: str = n.str;
// #51: pkg.alias type refs land here as a single TNAME whose // #51: pkg.alias type refs land here as a single TNAME whose
// str is the joined form (lib/ww/parse/parse.ww:258-265 in // str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in // parsetype). Split on the rightmost '.' and bind the leaf in
@@ -568,8 +570,21 @@ fn resolvealias(c: *checker, n: *node) *node = {
}; };
}; };
}; };
if (s == nil) { return nil; };
if (s.skind != skind.SK_TYPE) { return nil; };
return s;
};
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return
// the typedecl's body (possibly recursively). Pass-through for any
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a
// name we can't resolve.
fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; }; if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil; let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; }; if (s.decl != nil) { body = s.decl.lhs; };
if (body == nil) { return cur; }; if (body == nil) { return cur; };
@@ -1138,27 +1153,47 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; };
if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) { if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse. // #64 Phase-N step 2 (THE FLIP): build a per-decl
// Mirrors astsize's TNAME fallback so the helpers stay in // TY_NAMED wrapper instead of collapsing the alias to
// lockstep until A.2 collapses each cgen size-walker onto // its underlying. sym.type_ caches the wrapper so every
// tinfo.size directly. // TNAME resolving to the same decl yields the SAME tinfo
// // pointer — ptr-identity IS nominal identity (the whole
// #61 A.4: bind the resolved body too so future // point; typeeq is the only consumer, wired in step 3).
// tinfofornode calls on either the TNAME or its target // CHAINS, not flatten: under is the IMMEDIATE body's
// short-circuit on the cache hit instead of re-walking // tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/ // — mirrors cstage's two-phase type_named
// TTAGGED cycle-break pattern (a self-referential // (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// struct field's *T → TNAME → body would otherwise // pass2 patches under/size off resolve_type(d->lhs),
// re-enter the same chain). // where resolve_typename returns the inner NAMED.
let body: *node = resolvealias(c, n); let s: *sym = aliassym(c, n);
if (body != nil && body != n) { if (s != nil) {
let cached2: *tinfo = tinfocachelookup(c.tc, body); if (s.type_ != nil) {
if (cached2 != nil) { r = s.type_;
r = cached2;
} else { } else {
r = tinfofornode(c, body); let body: *node = nil;
if (r != nil) { if (s.decl != nil) {
tinfocachebind(c.tc, body, r); body = unwrapbang(s.decl.lhs);
};
if (body != nil) {
// PRE-BIND before resolving under: a
// self-referential field (`type node =
// struct {next: *node}`) re-finds this
// NAMED via sym.type_ instead of re-
// entering the chain. Mirrors cstage
// pass1's sym->type install
// (check.c:1909/1917) ahead of pass2's
// under patch, and A.2's TSTRUCT/TFN/
// TTAGGED tinfocachebind cycle-break.
let named: *tinfo = typenamed(s.name, nil);
s.type_ = named;
let under: *tinfo = tinfofornode(c, body);
named.under = under;
if (under != nil) {
named.size = under.size;
named.align = under.align;
named.slotsize = under.slotsize;
};
r = named;
}; };
}; };
}; };

View File

@@ -7602,15 +7602,17 @@ fn unwrapbang(n: *node) *node = {
return n; return n;
}; };
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return // aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// the typedecl's body (possibly recursively). Pass-through for any // symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a // unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// name we can't resolve. // out of resolvealias so tinfofornode's TY_NAMED build can reach the
fn resolvealias(c: *checker, n: *node) *node = { // decl sym (nominal identity = sym.type_ ptr-identity) instead of
let cur: *node = n; // flattening to the underlying. Mirrors cstage resolve_typename
for (cur != nil) { // (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
if (cur.kind != nkind.N_TNAME) { return cur; }; fn aliassym(c: *checker, n: *node) *sym = {
let nm: str = cur.str; if (n == nil) { return nil; };
if (n.kind != nkind.N_TNAME) { return nil; };
let nm: str = n.str;
// #51: pkg.alias type refs land here as a single TNAME whose // #51: pkg.alias type refs land here as a single TNAME whose
// str is the joined form (lib/ww/parse/parse.ww:258-265 in // str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in // parsetype). Split on the rightmost '.' and bind the leaf in
@@ -7665,8 +7667,21 @@ fn resolvealias(c: *checker, n: *node) *node = {
}; };
}; };
}; };
if (s == nil) { return nil; };
if (s.skind != skind.SK_TYPE) { return nil; };
return s;
};
// resolvealias — if n is an nkind.N_TNAME pointing at a typedecl, return
// the typedecl's body (possibly recursively). Pass-through for any
// other node. The chain stops once we hit a non-nkind.N_TNAME node or a
// name we can't resolve.
fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; }; if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil; let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; }; if (s.decl != nil) { body = s.decl.lhs; };
if (body == nil) { return cur; }; if (body == nil) { return cur; };
@@ -8235,27 +8250,47 @@ fn tinfofornode(c: *checker, n: *node) *tinfo = {
if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; }; if (streq(nm, "untyped_bool")) { r = c.tc.tyuntypedbool; };
if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; }; if (streq(nm, "untyped_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) { if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse. // #64 Phase-N step 2 (THE FLIP): build a per-decl
// Mirrors astsize's TNAME fallback so the helpers stay in // TY_NAMED wrapper instead of collapsing the alias to
// lockstep until A.2 collapses each cgen size-walker onto // its underlying. sym.type_ caches the wrapper so every
// tinfo.size directly. // TNAME resolving to the same decl yields the SAME tinfo
// // pointer — ptr-identity IS nominal identity (the whole
// #61 A.4: bind the resolved body too so future // point; typeeq is the only consumer, wired in step 3).
// tinfofornode calls on either the TNAME or its target // CHAINS, not flatten: under is the IMMEDIATE body's
// short-circuit on the cache hit instead of re-walking // tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/ // — mirrors cstage's two-phase type_named
// TTAGGED cycle-break pattern (a self-referential // (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// struct field's *T → TNAME → body would otherwise // pass2 patches under/size off resolve_type(d->lhs),
// re-enter the same chain). // where resolve_typename returns the inner NAMED.
let body: *node = resolvealias(c, n); let s: *sym = aliassym(c, n);
if (body != nil && body != n) { if (s != nil) {
let cached2: *tinfo = tinfocachelookup(c.tc, body); if (s.type_ != nil) {
if (cached2 != nil) { r = s.type_;
r = cached2;
} else { } else {
r = tinfofornode(c, body); let body: *node = nil;
if (r != nil) { if (s.decl != nil) {
tinfocachebind(c.tc, body, r); body = unwrapbang(s.decl.lhs);
};
if (body != nil) {
// PRE-BIND before resolving under: a
// self-referential field (`type node =
// struct {next: *node}`) re-finds this
// NAMED via sym.type_ instead of re-
// entering the chain. Mirrors cstage
// pass1's sym->type install
// (check.c:1909/1917) ahead of pass2's
// under patch, and A.2's TSTRUCT/TFN/
// TTAGGED tinfocachebind cycle-break.
let named: *tinfo = typenamed(s.name, nil);
s.type_ = named;
let under: *tinfo = tinfofornode(c, body);
named.under = under;
if (under != nil) {
named.size = under.size;
named.align = under.align;
named.slotsize = under.slotsize;
};
r = named;
}; };
}; };
}; };