diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e0301fde..14a37d0d 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; }; }; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 91dc2947..c53a2fe2 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -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; }; }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 1080bbf9..2b3278ca 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; }; }; };