From 884dbb402bf43428b18c65d9e95d0ab4928641d5 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 28 May 2026 05:17:28 +0900 Subject: [PATCH] wcc: dot-lhs prefers SK_USE module over same-leaf type/fn name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wwstage checker resolved a module-qualified call/access mod.x by the same-module preference in scopelookupprefer, so when the importing package's name collides with a type/fn of the same leaf (package fnmatch with fn fnmatch; package random with type random), the dot-lhs mod resolved to the same-leaf SK_TYPE/SK_FN instead of the coexisting SK_USE import — the N_DOT module-qual arm never fired and the call went nil-stamped (the D class of the asserttyped gap audit: fnmatch 2, random 16). cstage resolves this via Sym.use_alias; this ports the equivalent to wwstage. Add scopelookupuselocal (a single-scope SK_USE lookup, twin of scopelookuptype) and prefer SK_USE for a dot-lhs in exprtype's N_CALL and N_DOT arms, keyed on the scope where scopelookupprefer landed so a local binding sharing a module's leaf keeps value semantics. Scope-layer only — no type-identity touch (cstage use_alias never reaches type_eq). Drives the 901 gap-corpus D count to 0 (random_test now byte-id cs==ww). Compiler binary unchanged (no such collision in its own source); 990-997 hold. The separate fnmatch bare-enum-member cgen cs!=ww is unrelated (filed). --- lib/ww/sym.ww | 41 +++++++++++ selfhost/cmd/w6c/main.combined.ww | 104 +++++++++++++++++++++------ selfhost/cmd/wcc/check.ww | 63 ++++++++++------ selfhost/cmd/wwdump/main.combined.ww | 104 +++++++++++++++++++++------ test/wcc/901_asserttyped_gap.c | 10 +-- 5 files changed, 254 insertions(+), 68 deletions(-) diff --git a/lib/ww/sym.ww b/lib/ww/sym.ww index 35c6499b..0c78e7f1 100644 --- a/lib/ww/sym.ww +++ b/lib/ww/sym.ww @@ -126,6 +126,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { return nil; }; +// scopelookupuselocal — find a same-leaf SK_USE entry within ONE scope. +// +// Same FNV bucket + hashnext chain as scopelookuplocal, with a +// `skind == SK_USE` filter and NO parent walk. The dot-lhs twin of +// scopelookuptype: when a `use mod;` and a colliding top-level +// `fn mod` / `type mod` of the same leaf coexist (random.random, +// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the +// SK_FN/SK_TYPE whose mod matches the importing unit's package, masking +// the SK_USE. A dot-lhs `mod.x` must resolve `mod` to the SK_USE for the +// module-qualified arm to fire, so the resolver re-resolves through this +// filter — keyed on the scope where scopelookupprefer LANDED — when it +// lands on a non-USE same-leaf entry. +// +// Single-scope (not a parent walk) so a local binding that shares a leaf +// with a top-level `use` keeps value semantics: scopelookupprefer +// resolves the local in its inner scope, whose bucket holds no SK_USE, +// so this returns nil and the dot stays field access. Only a genuine +// same-scope coexistence (top-level use + top-level type/fn) re-resolves. +// +// This is the coexistence-equivalent of cstage's Sym.use_alias bit +// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by +// the `kind == SK_USE || use_alias` dot guards in resolve_typename and +// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf +// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww +// installdecl), so no flag is needed — the SK_USE is never overwritten, +// only out-preferred. Cite: project memory module_type_name_collision +// (cstage fix 2026-05-13). +export fn scopelookupuselocal(s: *scope, name: str) *sym = { + if (s == nil) { return nil; }; + let h: u64 = hashstr(name); + let bi: i32 = (h % (s.nbuckets: u64)): i32; + let b: *sym = s.buckets[bi]; + for (b != nil) { + if (streq(b.name, name)) { + if (b.skind == skind.SK_USE) { return b; }; + }; + b = b.hashnext; + }; + return nil; +}; + // scopelookupinmodule — module-filtered chain walk. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, plus diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index d25413c3..88948782 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -9877,6 +9877,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { return nil; }; +// scopelookupuselocal — find a same-leaf SK_USE entry within ONE scope. +// +// Same FNV bucket + hashnext chain as scopelookuplocal, with a +// `skind == SK_USE` filter and NO parent walk. The dot-lhs twin of +// scopelookuptype: when a `use mod;` and a colliding top-level +// `fn mod` / `type mod` of the same leaf coexist (random.random, +// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the +// SK_FN/SK_TYPE whose mod matches the importing unit's package, masking +// the SK_USE. A dot-lhs `mod.x` must resolve `mod` to the SK_USE for the +// module-qualified arm to fire, so the resolver re-resolves through this +// filter — keyed on the scope where scopelookupprefer LANDED — when it +// lands on a non-USE same-leaf entry. +// +// Single-scope (not a parent walk) so a local binding that shares a leaf +// with a top-level `use` keeps value semantics: scopelookupprefer +// resolves the local in its inner scope, whose bucket holds no SK_USE, +// so this returns nil and the dot stays field access. Only a genuine +// same-scope coexistence (top-level use + top-level type/fn) re-resolves. +// +// This is the coexistence-equivalent of cstage's Sym.use_alias bit +// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by +// the `kind == SK_USE || use_alias` dot guards in resolve_typename and +// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf +// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww +// installdecl), so no flag is needed — the SK_USE is never overwritten, +// only out-preferred. Cite: project memory module_type_name_collision +// (cstage fix 2026-05-13). +export fn scopelookupuselocal(s: *scope, name: str) *sym = { + if (s == nil) { return nil; }; + let h: u64 = hashstr(name); + let bi: i32 = (h % (s.nbuckets: u64)): i32; + let b: *sym = s.buckets[bi]; + for (b != nil) { + if (streq(b.name, name)) { + if (b.skind == skind.SK_USE) { return b; }; + }; + b = b.hashnext; + }; + return nil; +}; + // scopelookupinmodule — module-filtered chain walk. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, plus @@ -10185,16 +10226,21 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = { // Architectural note: wwstage uses COEXISTENCE rather than the cstage // promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any // same-leaf SK_TYPE/SK_FN/SK_DEF/SK_VAR live as separate entries in -// the same scope-bucket, distinguished by `sym.mod`. The dot-prefix -// lookup in resolvewalk + scopelookupinmodule's mod-filter already -// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same -// leaf — no `use_alias` flag needed. So the cstage L1722-class bug -// (promotion missing use_alias) is structurally non-reachable here. -// Don't port the use_alias flag from cstage without first re-reading -// the architecture: adding a field to `sym` changes its size and risks -// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage -// checkfile pass) will reconsider this when wwstage grows a real check -// pass on the cgen path. +// the same scope-bucket, distinguished by `sym.mod`. This avoids the +// cstage use_alias FLAG (a field on the sym, which would grow its size +// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the +// flag's RESOLUTION job still has to be done. When the colliding decl's +// package equals the importing unit's curmod (the `package fnmatch;` / +// `package random;` self-import: random.random, fnmatch.fnmatch), the +// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE, +// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the +// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs +// resolvers in exprtype's N_CALL and N_DOT arms re-resolve through +// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the +// landed scope — the coexistence-equivalent of cstage's use_alias bit. +// Cite: project memory module_type_name_collision (cstage fix +// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors +// below when wwstage grows a real check pass on the cgen path. // TODO(#11): cstage check.c errors on duplicate top-level type/def/fn // (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate ") and on // duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s") @@ -12254,11 +12300,15 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // flat scope — wrong on a cross-module shadow (753_convwrap_audit: // alpha.foo (i64,str) vs beta.foo (i64,i64)). // - // THE SK_USE GATE is the #6a-D separator: a D-class callee whose - // `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's - // module-leaf==type-name collision) does NOT resolve to SK_USE, so - // it falls through to the bare scopelookup and stays mis/unresolved. - // That nominal-collision is its own fold (#6a-D); not fixed here. + // #6a-D: a D-class callee whose `mod` leaf is itself a type/fn + // (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision) + // resolves through scopelookupprefer to that same-leaf entry, not + // the coexisting SK_USE, when curmod matches the colliding decl's + // package — so the SK_USE gate below misses and the call stays + // nil-stamped. scopelookupuselocal re-resolves `mod` to the SK_USE + // that coexists in the same scope (coexistence-equivalent of + // cstage's use_alias; see lib/ww/sym.ww + memory + // module_type_name_collision). let s: *sym = nil; if (callee.kind == nkind.N_IDENT) { s = scopelookupprefer(c.cur, c.curmod, nm); @@ -12266,6 +12316,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let ms: *sym = nil; if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) { ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str); + if (mu != nil) { ms = mu; }; + }; }; if (ms != nil && ms.skind == skind.SK_USE) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); @@ -12285,15 +12339,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c // :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands - // in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates - // — see installdecl docstring at L195-207); SK_USE alone gates - // case 1. Enum-member fold delegates non-literal lhs shapes - // (sibling backref, unary, binary, shift) to enumvalfold, - // matching cstage cmd/wcc/check.c:210-284 and harec's enum- - // resolve constexpr set at ref/harec/src/check.c:4419-4434. + // in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision + // (random.random / fnmatch.fnmatch — the module leaf is also a + // same-scope type/fn) re-resolves through scopelookupuselocal so + // the SK_USE coexisting alongside the type/fn wins (the + // coexistence-equivalent of cstage's use_alias; see installdecl + // docstring + lib/ww/sym.ww). Enum-member fold delegates + // non-literal lhs shapes (sibling backref, unary, binary, shift) + // to enumvalfold, matching cstage cmd/wcc/check.c:210-284 and + // harec's enum-resolve constexpr set at + // ref/harec/src/check.c:4419-4434. let lhsn: *node = e.lhs; if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) { let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, lhsn.str); + if (mu != nil) { ms = mu; }; + }; if (ms != nil) { // Fold case 1: module-qualified ref. Mirror cstage // check.c:749-775. cstage returns ty_err on SK_USE diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 6c9ec5c4..913ad10e 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -196,16 +196,21 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = { // Architectural note: wwstage uses COEXISTENCE rather than the cstage // promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any // same-leaf SK_TYPE/SK_FN/SK_DEF/SK_VAR live as separate entries in -// the same scope-bucket, distinguished by `sym.mod`. The dot-prefix -// lookup in resolvewalk + scopelookupinmodule's mod-filter already -// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same -// leaf — no `use_alias` flag needed. So the cstage L1722-class bug -// (promotion missing use_alias) is structurally non-reachable here. -// Don't port the use_alias flag from cstage without first re-reading -// the architecture: adding a field to `sym` changes its size and risks -// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage -// checkfile pass) will reconsider this when wwstage grows a real check -// pass on the cgen path. +// the same scope-bucket, distinguished by `sym.mod`. This avoids the +// cstage use_alias FLAG (a field on the sym, which would grow its size +// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the +// flag's RESOLUTION job still has to be done. When the colliding decl's +// package equals the importing unit's curmod (the `package fnmatch;` / +// `package random;` self-import: random.random, fnmatch.fnmatch), the +// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE, +// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the +// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs +// resolvers in exprtype's N_CALL and N_DOT arms re-resolve through +// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the +// landed scope — the coexistence-equivalent of cstage's use_alias bit. +// Cite: project memory module_type_name_collision (cstage fix +// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors +// below when wwstage grows a real check pass on the cgen path. // TODO(#11): cstage check.c errors on duplicate top-level type/def/fn // (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate ") and on // duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s") @@ -2265,11 +2270,15 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // flat scope — wrong on a cross-module shadow (753_convwrap_audit: // alpha.foo (i64,str) vs beta.foo (i64,i64)). // - // THE SK_USE GATE is the #6a-D separator: a D-class callee whose - // `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's - // module-leaf==type-name collision) does NOT resolve to SK_USE, so - // it falls through to the bare scopelookup and stays mis/unresolved. - // That nominal-collision is its own fold (#6a-D); not fixed here. + // #6a-D: a D-class callee whose `mod` leaf is itself a type/fn + // (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision) + // resolves through scopelookupprefer to that same-leaf entry, not + // the coexisting SK_USE, when curmod matches the colliding decl's + // package — so the SK_USE gate below misses and the call stays + // nil-stamped. scopelookupuselocal re-resolves `mod` to the SK_USE + // that coexists in the same scope (coexistence-equivalent of + // cstage's use_alias; see lib/ww/sym.ww + memory + // module_type_name_collision). let s: *sym = nil; if (callee.kind == nkind.N_IDENT) { s = scopelookupprefer(c.cur, c.curmod, nm); @@ -2277,6 +2286,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let ms: *sym = nil; if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) { ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str); + if (mu != nil) { ms = mu; }; + }; }; if (ms != nil && ms.skind == skind.SK_USE) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); @@ -2296,15 +2309,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c // :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands - // in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates - // — see installdecl docstring at L195-207); SK_USE alone gates - // case 1. Enum-member fold delegates non-literal lhs shapes - // (sibling backref, unary, binary, shift) to enumvalfold, - // matching cstage cmd/wcc/check.c:210-284 and harec's enum- - // resolve constexpr set at ref/harec/src/check.c:4419-4434. + // in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision + // (random.random / fnmatch.fnmatch — the module leaf is also a + // same-scope type/fn) re-resolves through scopelookupuselocal so + // the SK_USE coexisting alongside the type/fn wins (the + // coexistence-equivalent of cstage's use_alias; see installdecl + // docstring + lib/ww/sym.ww). Enum-member fold delegates + // non-literal lhs shapes (sibling backref, unary, binary, shift) + // to enumvalfold, matching cstage cmd/wcc/check.c:210-284 and + // harec's enum-resolve constexpr set at + // ref/harec/src/check.c:4419-4434. let lhsn: *node = e.lhs; if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) { let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, lhsn.str); + if (mu != nil) { ms = mu; }; + }; if (ms != nil) { // Fold case 1: module-qualified ref. Mirror cstage // check.c:749-775. cstage returns ty_err on SK_USE diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9ec0b21c..96ca22e2 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -9877,6 +9877,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = { return nil; }; +// scopelookupuselocal — find a same-leaf SK_USE entry within ONE scope. +// +// Same FNV bucket + hashnext chain as scopelookuplocal, with a +// `skind == SK_USE` filter and NO parent walk. The dot-lhs twin of +// scopelookuptype: when a `use mod;` and a colliding top-level +// `fn mod` / `type mod` of the same leaf coexist (random.random, +// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the +// SK_FN/SK_TYPE whose mod matches the importing unit's package, masking +// the SK_USE. A dot-lhs `mod.x` must resolve `mod` to the SK_USE for the +// module-qualified arm to fire, so the resolver re-resolves through this +// filter — keyed on the scope where scopelookupprefer LANDED — when it +// lands on a non-USE same-leaf entry. +// +// Single-scope (not a parent walk) so a local binding that shares a leaf +// with a top-level `use` keeps value semantics: scopelookupprefer +// resolves the local in its inner scope, whose bucket holds no SK_USE, +// so this returns nil and the dot stays field access. Only a genuine +// same-scope coexistence (top-level use + top-level type/fn) re-resolves. +// +// This is the coexistence-equivalent of cstage's Sym.use_alias bit +// (cmd/wcc/check.c: set at the SK_USE→SK_X promotion sites, consulted by +// the `kind == SK_USE || use_alias` dot guards in resolve_typename and +// cexpr's N_DOT arm). Wwstage installs the SK_USE and the same-leaf +// type/fn as SEPARATE coexisting entries (see selfhost/cmd/wcc/check.ww +// installdecl), so no flag is needed — the SK_USE is never overwritten, +// only out-preferred. Cite: project memory module_type_name_collision +// (cstage fix 2026-05-13). +export fn scopelookupuselocal(s: *scope, name: str) *sym = { + if (s == nil) { return nil; }; + let h: u64 = hashstr(name); + let bi: i32 = (h % (s.nbuckets: u64)): i32; + let b: *sym = s.buckets[bi]; + for (b != nil) { + if (streq(b.name, name)) { + if (b.skind == skind.SK_USE) { return b; }; + }; + b = b.hashnext; + }; + return nil; +}; + // scopelookupinmodule — module-filtered chain walk. // // Same FNV bucket + hashnext chain + parent walk as scopelookup, plus @@ -10185,16 +10226,21 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = { // Architectural note: wwstage uses COEXISTENCE rather than the cstage // promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any // same-leaf SK_TYPE/SK_FN/SK_DEF/SK_VAR live as separate entries in -// the same scope-bucket, distinguished by `sym.mod`. The dot-prefix -// lookup in resolvewalk + scopelookupinmodule's mod-filter already -// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same -// leaf — no `use_alias` flag needed. So the cstage L1722-class bug -// (promotion missing use_alias) is structurally non-reachable here. -// Don't port the use_alias flag from cstage without first re-reading -// the architecture: adding a field to `sym` changes its size and risks -// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage -// checkfile pass) will reconsider this when wwstage grows a real check -// pass on the cgen path. +// the same scope-bucket, distinguished by `sym.mod`. This avoids the +// cstage use_alias FLAG (a field on the sym, which would grow its size +// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the +// flag's RESOLUTION job still has to be done. When the colliding decl's +// package equals the importing unit's curmod (the `package fnmatch;` / +// `package random;` self-import: random.random, fnmatch.fnmatch), the +// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE, +// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the +// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs +// resolvers in exprtype's N_CALL and N_DOT arms re-resolve through +// scopelookupuselocal (lib/ww/sym.ww) to the SK_USE that coexists in the +// landed scope — the coexistence-equivalent of cstage's use_alias bit. +// Cite: project memory module_type_name_collision (cstage fix +// 2026-05-13). #11 (wwstage checkfile pass) revisits the dup-decl errors +// below when wwstage grows a real check pass on the cgen path. // TODO(#11): cstage check.c errors on duplicate top-level type/def/fn // (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate ") and on // duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s") @@ -12254,11 +12300,15 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { // flat scope — wrong on a cross-module shadow (753_convwrap_audit: // alpha.foo (i64,str) vs beta.foo (i64,i64)). // - // THE SK_USE GATE is the #6a-D separator: a D-class callee whose - // `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's - // module-leaf==type-name collision) does NOT resolve to SK_USE, so - // it falls through to the bare scopelookup and stays mis/unresolved. - // That nominal-collision is its own fold (#6a-D); not fixed here. + // #6a-D: a D-class callee whose `mod` leaf is itself a type/fn + // (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision) + // resolves through scopelookupprefer to that same-leaf entry, not + // the coexisting SK_USE, when curmod matches the colliding decl's + // package — so the SK_USE gate below misses and the call stays + // nil-stamped. scopelookupuselocal re-resolves `mod` to the SK_USE + // that coexists in the same scope (coexistence-equivalent of + // cstage's use_alias; see lib/ww/sym.ww + memory + // module_type_name_collision). let s: *sym = nil; if (callee.kind == nkind.N_IDENT) { s = scopelookupprefer(c.cur, c.curmod, nm); @@ -12266,6 +12316,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { let ms: *sym = nil; if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) { ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, callee.lhs.str); + if (mu != nil) { ms = mu; }; + }; }; if (ms != nil && ms.skind == skind.SK_USE) { s = scopelookupinmodule(c.cur, callee.lhs.str, nm); @@ -12285,15 +12339,23 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = { if (k == nkind.N_DOT) { // A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c // :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands - // in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates - // — see installdecl docstring at L195-207); SK_USE alone gates - // case 1. Enum-member fold delegates non-literal lhs shapes - // (sibling backref, unary, binary, shift) to enumvalfold, - // matching cstage cmd/wcc/check.c:210-284 and harec's enum- - // resolve constexpr set at ref/harec/src/check.c:4419-4434. + // in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision + // (random.random / fnmatch.fnmatch — the module leaf is also a + // same-scope type/fn) re-resolves through scopelookupuselocal so + // the SK_USE coexisting alongside the type/fn wins (the + // coexistence-equivalent of cstage's use_alias; see installdecl + // docstring + lib/ww/sym.ww). Enum-member fold delegates + // non-literal lhs shapes (sibling backref, unary, binary, shift) + // to enumvalfold, matching cstage cmd/wcc/check.c:210-284 and + // harec's enum-resolve constexpr set at + // ref/harec/src/check.c:4419-4434. let lhsn: *node = e.lhs; if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) { let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str); + if (ms != nil && ms.skind != skind.SK_USE) { + let mu: *sym = scopelookupuselocal(ms.scope, lhsn.str); + if (mu != nil) { ms = mu; }; + }; if (ms != nil) { // Fold case 1: module-qualified ref. Mirror cstage // check.c:749-775. cstage returns ty_err on SK_USE diff --git a/test/wcc/901_asserttyped_gap.c b/test/wcc/901_asserttyped_gap.c index 0c07c193..afd5b77a 100644 --- a/test/wcc/901_asserttyped_gap.c +++ b/test/wcc/901_asserttyped_gap.c @@ -23,8 +23,8 @@ * A module-qual N_DOT call result checked_test 0 (closed) * B fn-ptr struct-field call smoke 3 * C abort intrinsic callee utf8 8 - * D module-leaf == type/fn name fnmatch 2 - * D module-leaf == type/fn name random 16 + * D module-leaf == type/fn name fnmatch 0 (closed) + * D module-leaf == type/fn name random 0 (closed) * * Exit code of wwdump_ww is intentionally not gated: the diagnostics * land on stderr regardless of the run's success, and arming the bail @@ -113,9 +113,9 @@ main(void) { "lib/encoding/utf8/utf8.combined.ww", "C abort intrinsic callee", 8 }, { "lib/fnmatch/fnmatchtest.combined.ww", - "D module-leaf == type/fn name", 2 }, + "D module-leaf == type/fn name", 0 }, { "lib/math/random/random_test.combined.ww", - "D module-leaf == type/fn name", 16 }, + "D module-leaf == type/fn name", 0 }, { NULL, NULL, 0 }, }; @@ -151,6 +151,6 @@ main(void) return 1; } printf("asserttyped_gap: ww-stage checker warn set matches manifest " - "on %d gap-corpus fixtures (B+C+D pinned, A closed)\n", n); + "on %d gap-corpus fixtures (B+C pinned, A+D closed)\n", n); return 0; }