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,6 +7602,76 @@ fn unwrapbang(n: *node) *node = {
return n;
};
// aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// out of resolvealias so tinfofornode's TY_NAMED build can reach the
// decl sym (nominal identity = sym.type_ ptr-identity) instead of
// flattening to the underlying. Mirrors cstage resolve_typename
// (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
fn aliassym(c: *checker, n: *node) *sym = {
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
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
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
@@ -7610,63 +7680,8 @@ fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let nm: str = cur.str;
// #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
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; };
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_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse.
// Mirrors astsize's TNAME fallback so the helpers stay in
// lockstep until A.2 collapses each cgen size-walker onto
// tinfo.size directly.
//
// #61 A.4: bind the resolved body too so future
// tinfofornode calls on either the TNAME or its target
// short-circuit on the cache hit instead of re-walking
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
// TTAGGED cycle-break pattern (a self-referential
// struct field's *T → TNAME → body would otherwise
// re-enter the same chain).
let body: *node = resolvealias(c, n);
if (body != nil && body != n) {
let cached2: *tinfo = tinfocachelookup(c.tc, body);
if (cached2 != nil) {
r = cached2;
// #64 Phase-N step 2 (THE FLIP): build a per-decl
// TY_NAMED wrapper instead of collapsing the alias to
// its underlying. sym.type_ caches the wrapper so every
// TNAME resolving to the same decl yields the SAME tinfo
// pointer — ptr-identity IS nominal identity (the whole
// point; typeeq is the only consumer, wired in step 3).
// CHAINS, not flatten: under is the IMMEDIATE body's
// tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// — mirrors cstage's two-phase type_named
// (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// pass2 patches under/size off resolve_type(d->lhs),
// where resolve_typename returns the inner NAMED.
let s: *sym = aliassym(c, n);
if (s != nil) {
if (s.type_ != nil) {
r = s.type_;
} else {
r = tinfofornode(c, body);
if (r != nil) {
tinfocachebind(c.tc, body, r);
let body: *node = nil;
if (s.decl != nil) {
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,6 +505,76 @@ fn unwrapbang(n: *node) *node = {
return n;
};
// aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// out of resolvealias so tinfofornode's TY_NAMED build can reach the
// decl sym (nominal identity = sym.type_ ptr-identity) instead of
// flattening to the underlying. Mirrors cstage resolve_typename
// (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
fn aliassym(c: *checker, n: *node) *sym = {
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
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
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
@@ -513,63 +583,8 @@ fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let nm: str = cur.str;
// #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
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; };
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_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse.
// Mirrors astsize's TNAME fallback so the helpers stay in
// lockstep until A.2 collapses each cgen size-walker onto
// tinfo.size directly.
//
// #61 A.4: bind the resolved body too so future
// tinfofornode calls on either the TNAME or its target
// short-circuit on the cache hit instead of re-walking
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
// TTAGGED cycle-break pattern (a self-referential
// struct field's *T → TNAME → body would otherwise
// re-enter the same chain).
let body: *node = resolvealias(c, n);
if (body != nil && body != n) {
let cached2: *tinfo = tinfocachelookup(c.tc, body);
if (cached2 != nil) {
r = cached2;
// #64 Phase-N step 2 (THE FLIP): build a per-decl
// TY_NAMED wrapper instead of collapsing the alias to
// its underlying. sym.type_ caches the wrapper so every
// TNAME resolving to the same decl yields the SAME tinfo
// pointer — ptr-identity IS nominal identity (the whole
// point; typeeq is the only consumer, wired in step 3).
// CHAINS, not flatten: under is the IMMEDIATE body's
// tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// — mirrors cstage's two-phase type_named
// (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// pass2 patches under/size off resolve_type(d->lhs),
// where resolve_typename returns the inner NAMED.
let s: *sym = aliassym(c, n);
if (s != nil) {
if (s.type_ != nil) {
r = s.type_;
} else {
r = tinfofornode(c, body);
if (r != nil) {
tinfocachebind(c.tc, body, r);
let body: *node = nil;
if (s.decl != nil) {
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,6 +7602,76 @@ fn unwrapbang(n: *node) *node = {
return n;
};
// aliassym — resolve a single nkind.N_TNAME to its IMMEDIATE type
// symbol (one level, no chain walk). Returns nil for non-TNAME nodes,
// unresolvable names, or non-SK_TYPE bindings. #64 factors the lookup
// out of resolvealias so tinfofornode's TY_NAMED build can reach the
// decl sym (nominal identity = sym.type_ ptr-identity) instead of
// flattening to the underlying. Mirrors cstage resolve_typename
// (cmd/wcc/check.c:60-88), which returns the sym's NAMED, not the base.
fn aliassym(c: *checker, n: *node) *sym = {
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
// str is the joined form (lib/ww/parse/parse.ww:258-265 in
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
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
@@ -7610,63 +7680,8 @@ fn resolvealias(c: *checker, n: *node) *node = {
let cur: *node = n;
for (cur != nil) {
if (cur.kind != nkind.N_TNAME) { return cur; };
let nm: str = cur.str;
// #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
// parsetype). Split on the rightmost '.' and bind the leaf in
// the head module's scope. Mirrors cstage resolve_typename
// cmd/wcc/check.c:74-83 strrchr branch — without this the
// raw `os.oserror` lookup misses and checkisas false-positives
// every cross-module tagged scrutinee.
let dotidx: i32 = -1;
let i: i32 = 0;
for (i < nm.len) {
if (nm[i] == 46u8) { dotidx = i; };
i += 1;
};
let s: *sym = nil;
if (dotidx >= 0) {
let head: str;
head.ptr = nm.ptr;
head.len = dotidx;
let leaf: str;
leaf.ptr = nm.ptr + ((dotidx + 1): u64);
leaf.len = nm.len - dotidx - 1;
s = scopelookupinmodule(c.cur, head, leaf);
} else {
// #53: same-module preference. Mirrors cstage
// cmd/wcc/check.c:66 scope_lookup_prefer. Without this,
// two modules each declaring `type invalid = ...` collide
// on the head-first bucket walk: e.g. utf8.invalid `!void`
// vs strconv.invalid `!i32` resolves to whichever
// registered first, driving localloadop MOVSXD/MOVQ
// divergence at 994/995. Other bare-leaf callers in this
// file (L1597 exprtype N_IDENT, L1795/L2720 N_DOT-callee
// leaf, L600 varianterr, L647 scruttype) tracked as #55.
s = scopelookupprefer(c.cur, c.curmod, nm);
// #61 A.5: bare TNAME that collides with an imported
// module bareword. Two shapes hit this:
// - `let l: lex;` where `lex` struct lives in
// `package lex;` (mod matches leaf).
// - `let t: tok;` where `tok` struct lives in
// `package lex;` (mod differs from leaf — tok.ww
// declares `package lex;`).
// scopelookup bucket-walks the flat scope and can land
// on the SK_USE entry first; without the fallback we'd
// return the unresolved TNAME and tinfofornode aborts on
// body == n. scopelookuptype walks the same bucket but
// filters on SK_TYPE so the struct entry surfaces
// regardless of its declaring package. Mirrors the
// bare-vs-qualified pattern from task #57.
if (s != nil) {
if (s.skind != skind.SK_TYPE) {
let sm: *sym = scopelookuptype(c.cur, nm);
if (sm != nil) { s = sm; };
};
};
};
let s: *sym = aliassym(c, cur);
if (s == nil) { return cur; };
if (s.skind != skind.SK_TYPE) { return cur; };
let body: *node = nil;
if (s.decl != nil) { body = s.decl.lhs; };
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_nil")) { r = c.tc.tyuntypednil; };
if (r == nil) {
// Alias / user-defined name: resolve via scope and recurse.
// Mirrors astsize's TNAME fallback so the helpers stay in
// lockstep until A.2 collapses each cgen size-walker onto
// tinfo.size directly.
//
// #61 A.4: bind the resolved body too so future
// tinfofornode calls on either the TNAME or its target
// short-circuit on the cache hit instead of re-walking
// the chain. Pre-bind matches A.2's TSTRUCT/TFN/TTUPLE/
// TTAGGED cycle-break pattern (a self-referential
// struct field's *T → TNAME → body would otherwise
// re-enter the same chain).
let body: *node = resolvealias(c, n);
if (body != nil && body != n) {
let cached2: *tinfo = tinfocachelookup(c.tc, body);
if (cached2 != nil) {
r = cached2;
// #64 Phase-N step 2 (THE FLIP): build a per-decl
// TY_NAMED wrapper instead of collapsing the alias to
// its underlying. sym.type_ caches the wrapper so every
// TNAME resolving to the same decl yields the SAME tinfo
// pointer — ptr-identity IS nominal identity (the whole
// point; typeeq is the only consumer, wired in step 3).
// CHAINS, not flatten: under is the IMMEDIATE body's
// tinfo, so `type a = b` gives NAMED(a).under = NAMED(b)
// — mirrors cstage's two-phase type_named
// (cmd/wcc/check.c:1900-1929): pass1 creates the NAMED,
// pass2 patches under/size off resolve_type(d->lhs),
// where resolve_typename returns the inner NAMED.
let s: *sym = aliassym(c, n);
if (s != nil) {
if (s.type_ != nil) {
r = s.type_;
} else {
r = tinfofornode(c, body);
if (r != nil) {
tinfocachebind(c.tc, body, r);
let body: *node = nil;
if (s.decl != nil) {
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;
};
};
};