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:
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user