wcc: dot-lhs prefers SK_USE module over same-leaf type/fn name
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).
This commit is contained in:
@@ -126,6 +126,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = {
|
|||||||
return nil;
|
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.
|
// scopelookupinmodule — module-filtered chain walk.
|
||||||
//
|
//
|
||||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
||||||
|
|||||||
@@ -9877,6 +9877,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = {
|
|||||||
return nil;
|
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.
|
// scopelookupinmodule — module-filtered chain walk.
|
||||||
//
|
//
|
||||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
// 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
|
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
||||||
// promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any
|
// 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
|
// 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
|
// the same scope-bucket, distinguished by `sym.mod`. This avoids the
|
||||||
// lookup in resolvewalk + scopelookupinmodule's mod-filter already
|
// cstage use_alias FLAG (a field on the sym, which would grow its size
|
||||||
// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same
|
// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the
|
||||||
// leaf — no `use_alias` flag needed. So the cstage L1722-class bug
|
// flag's RESOLUTION job still has to be done. When the colliding decl's
|
||||||
// (promotion missing use_alias) is structurally non-reachable here.
|
// package equals the importing unit's curmod (the `package fnmatch;` /
|
||||||
// Don't port the use_alias flag from cstage without first re-reading
|
// `package random;` self-import: random.random, fnmatch.fnmatch), the
|
||||||
// the architecture: adding a field to `sym` changes its size and risks
|
// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE,
|
||||||
// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage
|
// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the
|
||||||
// checkfile pass) will reconsider this when wwstage grows a real check
|
// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs
|
||||||
// pass on the cgen path.
|
// 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
|
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
// 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:
|
// flat scope — wrong on a cross-module shadow (753_convwrap_audit:
|
||||||
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
||||||
//
|
//
|
||||||
// THE SK_USE GATE is the #6a-D separator: a D-class callee whose
|
// #6a-D: a D-class callee whose `mod` leaf is itself a type/fn
|
||||||
// `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's
|
// (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision)
|
||||||
// module-leaf==type-name collision) does NOT resolve to SK_USE, so
|
// resolves through scopelookupprefer to that same-leaf entry, not
|
||||||
// it falls through to the bare scopelookup and stays mis/unresolved.
|
// the coexisting SK_USE, when curmod matches the colliding decl's
|
||||||
// That nominal-collision is its own fold (#6a-D); not fixed here.
|
// 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;
|
let s: *sym = nil;
|
||||||
if (callee.kind == nkind.N_IDENT) {
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||||
@@ -12266,6 +12316,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
let ms: *sym = nil;
|
let ms: *sym = nil;
|
||||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
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) {
|
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
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) {
|
if (k == nkind.N_DOT) {
|
||||||
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
||||||
// — see installdecl docstring at L195-207); SK_USE alone gates
|
// (random.random / fnmatch.fnmatch — the module leaf is also a
|
||||||
// case 1. Enum-member fold delegates non-literal lhs shapes
|
// same-scope type/fn) re-resolves through scopelookupuselocal so
|
||||||
// (sibling backref, unary, binary, shift) to enumvalfold,
|
// the SK_USE coexisting alongside the type/fn wins (the
|
||||||
// matching cstage cmd/wcc/check.c:210-284 and harec's enum-
|
// coexistence-equivalent of cstage's use_alias; see installdecl
|
||||||
// resolve constexpr set at ref/harec/src/check.c:4419-4434.
|
// 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;
|
let lhsn: *node = e.lhs;
|
||||||
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
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) {
|
if (ms != nil) {
|
||||||
// Fold case 1: module-qualified ref. Mirror cstage
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
// check.c:749-775. cstage returns ty_err on SK_USE
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
|||||||
@@ -196,16 +196,21 @@ fn checkmoduleshadow(c: *checker, name: str, kindstr: str) void = {
|
|||||||
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
||||||
// promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any
|
// 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
|
// 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
|
// the same scope-bucket, distinguished by `sym.mod`. This avoids the
|
||||||
// lookup in resolvewalk + scopelookupinmodule's mod-filter already
|
// cstage use_alias FLAG (a field on the sym, which would grow its size
|
||||||
// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same
|
// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the
|
||||||
// leaf — no `use_alias` flag needed. So the cstage L1722-class bug
|
// flag's RESOLUTION job still has to be done. When the colliding decl's
|
||||||
// (promotion missing use_alias) is structurally non-reachable here.
|
// package equals the importing unit's curmod (the `package fnmatch;` /
|
||||||
// Don't port the use_alias flag from cstage without first re-reading
|
// `package random;` self-import: random.random, fnmatch.fnmatch), the
|
||||||
// the architecture: adding a field to `sym` changes its size and risks
|
// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE,
|
||||||
// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage
|
// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the
|
||||||
// checkfile pass) will reconsider this when wwstage grows a real check
|
// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs
|
||||||
// pass on the cgen path.
|
// 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
|
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
// 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:
|
// flat scope — wrong on a cross-module shadow (753_convwrap_audit:
|
||||||
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
||||||
//
|
//
|
||||||
// THE SK_USE GATE is the #6a-D separator: a D-class callee whose
|
// #6a-D: a D-class callee whose `mod` leaf is itself a type/fn
|
||||||
// `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's
|
// (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision)
|
||||||
// module-leaf==type-name collision) does NOT resolve to SK_USE, so
|
// resolves through scopelookupprefer to that same-leaf entry, not
|
||||||
// it falls through to the bare scopelookup and stays mis/unresolved.
|
// the coexisting SK_USE, when curmod matches the colliding decl's
|
||||||
// That nominal-collision is its own fold (#6a-D); not fixed here.
|
// 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;
|
let s: *sym = nil;
|
||||||
if (callee.kind == nkind.N_IDENT) {
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||||
@@ -2277,6 +2286,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
let ms: *sym = nil;
|
let ms: *sym = nil;
|
||||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
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) {
|
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
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) {
|
if (k == nkind.N_DOT) {
|
||||||
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
||||||
// — see installdecl docstring at L195-207); SK_USE alone gates
|
// (random.random / fnmatch.fnmatch — the module leaf is also a
|
||||||
// case 1. Enum-member fold delegates non-literal lhs shapes
|
// same-scope type/fn) re-resolves through scopelookupuselocal so
|
||||||
// (sibling backref, unary, binary, shift) to enumvalfold,
|
// the SK_USE coexisting alongside the type/fn wins (the
|
||||||
// matching cstage cmd/wcc/check.c:210-284 and harec's enum-
|
// coexistence-equivalent of cstage's use_alias; see installdecl
|
||||||
// resolve constexpr set at ref/harec/src/check.c:4419-4434.
|
// 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;
|
let lhsn: *node = e.lhs;
|
||||||
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
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) {
|
if (ms != nil) {
|
||||||
// Fold case 1: module-qualified ref. Mirror cstage
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
// check.c:749-775. cstage returns ty_err on SK_USE
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
|||||||
@@ -9877,6 +9877,47 @@ export fn scopelookuptype(s: *scope, name: str) *sym = {
|
|||||||
return nil;
|
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.
|
// scopelookupinmodule — module-filtered chain walk.
|
||||||
//
|
//
|
||||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
// 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
|
// Architectural note: wwstage uses COEXISTENCE rather than the cstage
|
||||||
// promote-SK_USE-in-place approach in cmd/wcc/check.c. SK_USE and any
|
// 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
|
// 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
|
// the same scope-bucket, distinguished by `sym.mod`. This avoids the
|
||||||
// lookup in resolvewalk + scopelookupinmodule's mod-filter already
|
// cstage use_alias FLAG (a field on the sym, which would grow its size
|
||||||
// disambiguate `fnmatch.flag` against an `fn fnmatch(...)` of the same
|
// and risk the wwstage cgen amalloc-undersize trap, rob-pike) — but the
|
||||||
// leaf — no `use_alias` flag needed. So the cstage L1722-class bug
|
// flag's RESOLUTION job still has to be done. When the colliding decl's
|
||||||
// (promotion missing use_alias) is structurally non-reachable here.
|
// package equals the importing unit's curmod (the `package fnmatch;` /
|
||||||
// Don't port the use_alias flag from cstage without first re-reading
|
// `package random;` self-import: random.random, fnmatch.fnmatch), the
|
||||||
// the architecture: adding a field to `sym` changes its size and risks
|
// mod-preferring scopelookupprefer returns the same-leaf SK_FN/SK_TYPE,
|
||||||
// the wwstage cgen amalloc-undersize trap (rob-pike). #11 (wwstage
|
// not the coexisting SK_USE, so a dot-lhs `mod.x` would miss the
|
||||||
// checkfile pass) will reconsider this when wwstage grows a real check
|
// module-qualified arm and the call nil-stamps (#6a-D). The dot-lhs
|
||||||
// pass on the cgen path.
|
// 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
|
// TODO(#11): cstage check.c errors on duplicate top-level type/def/fn
|
||||||
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
// (see cmd/wcc/check.c L1800/L1839/L1860 "duplicate <kind>") and on
|
||||||
// duplicate top-level let (cmd/wcc/check.c L1880, "duplicate let %s")
|
// 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:
|
// flat scope — wrong on a cross-module shadow (753_convwrap_audit:
|
||||||
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
// alpha.foo (i64,str) vs beta.foo (i64,i64)).
|
||||||
//
|
//
|
||||||
// THE SK_USE GATE is the #6a-D separator: a D-class callee whose
|
// #6a-D: a D-class callee whose `mod` leaf is itself a type/fn
|
||||||
// `mod` leaf is itself a type/fn (SK_TYPE/SK_FN — random/fnmatch's
|
// (SK_TYPE/SK_FN — random.random / fnmatch.fnmatch collision)
|
||||||
// module-leaf==type-name collision) does NOT resolve to SK_USE, so
|
// resolves through scopelookupprefer to that same-leaf entry, not
|
||||||
// it falls through to the bare scopelookup and stays mis/unresolved.
|
// the coexisting SK_USE, when curmod matches the colliding decl's
|
||||||
// That nominal-collision is its own fold (#6a-D); not fixed here.
|
// 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;
|
let s: *sym = nil;
|
||||||
if (callee.kind == nkind.N_IDENT) {
|
if (callee.kind == nkind.N_IDENT) {
|
||||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||||
@@ -12266,6 +12316,10 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
|||||||
let ms: *sym = nil;
|
let ms: *sym = nil;
|
||||||
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
if (callee.lhs != nil && callee.lhs.kind == nkind.N_IDENT) {
|
||||||
ms = scopelookupprefer(c.cur, c.curmod, callee.lhs.str);
|
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) {
|
if (ms != nil && ms.skind == skind.SK_USE) {
|
||||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
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) {
|
if (k == nkind.N_DOT) {
|
||||||
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
// A.6.1.5a — fold cases only. Mirrors cstage cmd/wcc/check.c
|
||||||
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
// :740-832. Struct field + pseudo-field (.len/.cap/.ptr) lands
|
||||||
// in A.6.1.5b. Wwstage has no use_alias (sym.mod disambiguates
|
// in A.6.1.5b. SK_USE gates case 1; a #6a-D dot-lhs collision
|
||||||
// — see installdecl docstring at L195-207); SK_USE alone gates
|
// (random.random / fnmatch.fnmatch — the module leaf is also a
|
||||||
// case 1. Enum-member fold delegates non-literal lhs shapes
|
// same-scope type/fn) re-resolves through scopelookupuselocal so
|
||||||
// (sibling backref, unary, binary, shift) to enumvalfold,
|
// the SK_USE coexisting alongside the type/fn wins (the
|
||||||
// matching cstage cmd/wcc/check.c:210-284 and harec's enum-
|
// coexistence-equivalent of cstage's use_alias; see installdecl
|
||||||
// resolve constexpr set at ref/harec/src/check.c:4419-4434.
|
// 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;
|
let lhsn: *node = e.lhs;
|
||||||
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
if (lhsn != nil) { if (lhsn.kind == nkind.N_IDENT) {
|
||||||
let ms: *sym = scopelookupprefer(c.cur, c.curmod, lhsn.str);
|
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) {
|
if (ms != nil) {
|
||||||
// Fold case 1: module-qualified ref. Mirror cstage
|
// Fold case 1: module-qualified ref. Mirror cstage
|
||||||
// check.c:749-775. cstage returns ty_err on SK_USE
|
// check.c:749-775. cstage returns ty_err on SK_USE
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
* A module-qual N_DOT call result checked_test 0 (closed)
|
* A module-qual N_DOT call result checked_test 0 (closed)
|
||||||
* B fn-ptr struct-field call smoke 3
|
* B fn-ptr struct-field call smoke 3
|
||||||
* C abort intrinsic callee utf8 8
|
* C abort intrinsic callee utf8 8
|
||||||
* D module-leaf == type/fn name fnmatch 2
|
* D module-leaf == type/fn name fnmatch 0 (closed)
|
||||||
* D module-leaf == type/fn name random 16
|
* D module-leaf == type/fn name random 0 (closed)
|
||||||
*
|
*
|
||||||
* Exit code of wwdump_ww is intentionally not gated: the diagnostics
|
* Exit code of wwdump_ww is intentionally not gated: the diagnostics
|
||||||
* land on stderr regardless of the run's success, and arming the bail
|
* 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",
|
{ "lib/encoding/utf8/utf8.combined.ww",
|
||||||
"C abort intrinsic callee", 8 },
|
"C abort intrinsic callee", 8 },
|
||||||
{ "lib/fnmatch/fnmatchtest.combined.ww",
|
{ "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",
|
{ "lib/math/random/random_test.combined.ww",
|
||||||
"D module-leaf == type/fn name", 16 },
|
"D module-leaf == type/fn name", 0 },
|
||||||
{ NULL, NULL, 0 },
|
{ NULL, NULL, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,6 +151,6 @@ main(void)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
printf("asserttyped_gap: ww-stage checker warn set matches manifest "
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user