Re-arming the wwstage asserttyped bail surfaced 94 nil-stamp warns in the checked corpus: let (a,b) = mod.fn() left its destructure bindings (and every use) unstamped because exprtype's N_CALL arm resolved an N_DOT callee by bare leaf — the gap its own comment flagged (#16/#17). Fix at the root: when an N_DOT callee's lhs resolves to SK_USE, resolve the result via scopelookupinmodule (mirror cstage cexpr check.c:1035 + cgen fnretlookupmod cgen.ww:2263). The N_MLET backfill then just consumes the resolved tuple, matching harec create_unpack_bindings (check.c:1354-1419), which does no callee resolution — single path, no third copy. The SK_USE gate leaves the module-leaf==type/fn-name collision cases (random/fnmatch) on bare lookup — that nominal-resolution gap is a separate fold. Beyond destructure, the root fix also closes a latent cs!=ww divergence on non-destructure cross-module same-leaf calls (a head-ordered shadow was mis-sizing the receive slot). asserttyped is ww-stage only, so the live ww-driver suite can't see this — the net is the warn count (checked 94->0, collision cases unchanged) + cs==ww .s (probe 956). Compiler binary unchanged; 990-997 byte-id hold.
This commit is contained in:
@@ -10379,29 +10379,31 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// (commit 2's bridge) needs: without it a `let (f,i)=mk()` f64 binding
|
||||
// reads stamp nil → would disagree with the structural f64.
|
||||
//
|
||||
// GUARD bare N_IDENT callee only: exprtype's N_CALL N_DOT arm is the
|
||||
// shelved #16/#17 cross-module-shadow gap (check.ww:2250-2253), so a
|
||||
// module-qualified callee resolves to the wrong/nil return type —
|
||||
// stamping off it would be worse than the nil it replaces. Bare same-
|
||||
// module callees resolve correctly today. Faithful to harec
|
||||
// create_unpack_bindings (ref/harec/src/check.c:1354-1416) and the
|
||||
// cstage twin (cmd/wcc/check.c:1912 N_MLET, which uses cexpr ungated);
|
||||
// narrowed to N_IDENT pending #16/#17. Annotated bindings keep their
|
||||
// own type. Mirrors the N_FORRANGE binding-install shape above; the
|
||||
// bindings would otherwise install (unstamped) via the generic N_LET
|
||||
// walk, so this early return must register them itself.
|
||||
// #6a-A: backfill off ANY call rhs, not just a bare N_IDENT callee, so
|
||||
// a module-qualified `let (res, ov) = checked.addi64(a, b)` (N_DOT
|
||||
// callee) stamps its bindings too. This is now a SINGLE path: just call
|
||||
// exprtype(rhs) and consume the resolved N_TTUPLE — no callee resolution
|
||||
// here. Harec's create_unpack_bindings does the same: ZERO callee
|
||||
// resolution, it walks an already-typed tuple result (ref/harec/src/
|
||||
// check.c:1354-1419). The module-qualified resolution that makes this
|
||||
// correct for an N_DOT callee lives at the ROOT, in exprtype's N_CALL
|
||||
// arm (the SK_USE-gated scopelookupinmodule there), so the binding just
|
||||
// consumes. A D-class module whose leaf collides with a type/fn name
|
||||
// resolves to nil/wrong-kind at the exprtype root (the SK_USE gate
|
||||
// fails) → no N_TTUPLE → those destructures stay unstamped, a separate
|
||||
// nominal-collision fold (#6a-D), not this one. Annotated bindings keep
|
||||
// their own type. Mirrors the N_FORRANGE binding-install shape above;
|
||||
// the bindings would otherwise install (unstamped) via the generic
|
||||
// N_LET walk, so this early return must register them itself.
|
||||
if (k == nkind.N_MLET) {
|
||||
if (n.rhs != nil) { resolvewalk(c, n.rhs); };
|
||||
let pt: *node = nil;
|
||||
if (n.rhs != nil) { if (n.rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) { if (callee.kind == nkind.N_IDENT) {
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
}; };
|
||||
let l: *node = n.list;
|
||||
@@ -12236,15 +12238,40 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
// Mirrors cstage cexpr N_IDENT routing through
|
||||
// scope_lookup_prefer with c->cur_mod. N_DOT keeps the bare
|
||||
// scopelookup — its module-qualified resolution is a separate
|
||||
// gap (parser stores the leaf in callee.str; mod is in
|
||||
// callee.lhs.str, not consumed here yet).
|
||||
// scope_lookup_prefer with c->cur_mod.
|
||||
//
|
||||
// #6a-A: a module-qualified `mod.fn()` callee resolves via the
|
||||
// callee.lhs module hint when `mod` is an import (SK_USE) —
|
||||
// scopelookupinmodule(mod, leaf) — mirroring cstage cexpr N_DOT
|
||||
// (cmd/wcc/check.c:1035 scope_lookup_in_module) and cgen's
|
||||
// rettupleof (cgen.ww:2263 fnretlookupmod). Closing this at the
|
||||
// N_CALL root (vs the N_MLET backfill) makes every consumer of a
|
||||
// module-qual call result — destructure binding AND a bare
|
||||
// `mod.fn().0` rvalue — read the right return type via the one
|
||||
// expr path, harec-faithful (binding-unpack does zero callee
|
||||
// resolution, ref/harec/src/check.c:1354-1419). Without it the
|
||||
// bare-leaf scopelookup grabbed whichever same-leaf fn heads the
|
||||
// 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.
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
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) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != skind.SK_FN) { return nil; };
|
||||
|
||||
@@ -390,29 +390,31 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// (commit 2's bridge) needs: without it a `let (f,i)=mk()` f64 binding
|
||||
// reads stamp nil → would disagree with the structural f64.
|
||||
//
|
||||
// GUARD bare N_IDENT callee only: exprtype's N_CALL N_DOT arm is the
|
||||
// shelved #16/#17 cross-module-shadow gap (check.ww:2250-2253), so a
|
||||
// module-qualified callee resolves to the wrong/nil return type —
|
||||
// stamping off it would be worse than the nil it replaces. Bare same-
|
||||
// module callees resolve correctly today. Faithful to harec
|
||||
// create_unpack_bindings (ref/harec/src/check.c:1354-1416) and the
|
||||
// cstage twin (cmd/wcc/check.c:1912 N_MLET, which uses cexpr ungated);
|
||||
// narrowed to N_IDENT pending #16/#17. Annotated bindings keep their
|
||||
// own type. Mirrors the N_FORRANGE binding-install shape above; the
|
||||
// bindings would otherwise install (unstamped) via the generic N_LET
|
||||
// walk, so this early return must register them itself.
|
||||
// #6a-A: backfill off ANY call rhs, not just a bare N_IDENT callee, so
|
||||
// a module-qualified `let (res, ov) = checked.addi64(a, b)` (N_DOT
|
||||
// callee) stamps its bindings too. This is now a SINGLE path: just call
|
||||
// exprtype(rhs) and consume the resolved N_TTUPLE — no callee resolution
|
||||
// here. Harec's create_unpack_bindings does the same: ZERO callee
|
||||
// resolution, it walks an already-typed tuple result (ref/harec/src/
|
||||
// check.c:1354-1419). The module-qualified resolution that makes this
|
||||
// correct for an N_DOT callee lives at the ROOT, in exprtype's N_CALL
|
||||
// arm (the SK_USE-gated scopelookupinmodule there), so the binding just
|
||||
// consumes. A D-class module whose leaf collides with a type/fn name
|
||||
// resolves to nil/wrong-kind at the exprtype root (the SK_USE gate
|
||||
// fails) → no N_TTUPLE → those destructures stay unstamped, a separate
|
||||
// nominal-collision fold (#6a-D), not this one. Annotated bindings keep
|
||||
// their own type. Mirrors the N_FORRANGE binding-install shape above;
|
||||
// the bindings would otherwise install (unstamped) via the generic
|
||||
// N_LET walk, so this early return must register them itself.
|
||||
if (k == nkind.N_MLET) {
|
||||
if (n.rhs != nil) { resolvewalk(c, n.rhs); };
|
||||
let pt: *node = nil;
|
||||
if (n.rhs != nil) { if (n.rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) { if (callee.kind == nkind.N_IDENT) {
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
}; };
|
||||
let l: *node = n.list;
|
||||
@@ -2247,15 +2249,40 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
// Mirrors cstage cexpr N_IDENT routing through
|
||||
// scope_lookup_prefer with c->cur_mod. N_DOT keeps the bare
|
||||
// scopelookup — its module-qualified resolution is a separate
|
||||
// gap (parser stores the leaf in callee.str; mod is in
|
||||
// callee.lhs.str, not consumed here yet).
|
||||
// scope_lookup_prefer with c->cur_mod.
|
||||
//
|
||||
// #6a-A: a module-qualified `mod.fn()` callee resolves via the
|
||||
// callee.lhs module hint when `mod` is an import (SK_USE) —
|
||||
// scopelookupinmodule(mod, leaf) — mirroring cstage cexpr N_DOT
|
||||
// (cmd/wcc/check.c:1035 scope_lookup_in_module) and cgen's
|
||||
// rettupleof (cgen.ww:2263 fnretlookupmod). Closing this at the
|
||||
// N_CALL root (vs the N_MLET backfill) makes every consumer of a
|
||||
// module-qual call result — destructure binding AND a bare
|
||||
// `mod.fn().0` rvalue — read the right return type via the one
|
||||
// expr path, harec-faithful (binding-unpack does zero callee
|
||||
// resolution, ref/harec/src/check.c:1354-1419). Without it the
|
||||
// bare-leaf scopelookup grabbed whichever same-leaf fn heads the
|
||||
// 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.
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
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) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != skind.SK_FN) { return nil; };
|
||||
|
||||
@@ -10379,29 +10379,31 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
// (commit 2's bridge) needs: without it a `let (f,i)=mk()` f64 binding
|
||||
// reads stamp nil → would disagree with the structural f64.
|
||||
//
|
||||
// GUARD bare N_IDENT callee only: exprtype's N_CALL N_DOT arm is the
|
||||
// shelved #16/#17 cross-module-shadow gap (check.ww:2250-2253), so a
|
||||
// module-qualified callee resolves to the wrong/nil return type —
|
||||
// stamping off it would be worse than the nil it replaces. Bare same-
|
||||
// module callees resolve correctly today. Faithful to harec
|
||||
// create_unpack_bindings (ref/harec/src/check.c:1354-1416) and the
|
||||
// cstage twin (cmd/wcc/check.c:1912 N_MLET, which uses cexpr ungated);
|
||||
// narrowed to N_IDENT pending #16/#17. Annotated bindings keep their
|
||||
// own type. Mirrors the N_FORRANGE binding-install shape above; the
|
||||
// bindings would otherwise install (unstamped) via the generic N_LET
|
||||
// walk, so this early return must register them itself.
|
||||
// #6a-A: backfill off ANY call rhs, not just a bare N_IDENT callee, so
|
||||
// a module-qualified `let (res, ov) = checked.addi64(a, b)` (N_DOT
|
||||
// callee) stamps its bindings too. This is now a SINGLE path: just call
|
||||
// exprtype(rhs) and consume the resolved N_TTUPLE — no callee resolution
|
||||
// here. Harec's create_unpack_bindings does the same: ZERO callee
|
||||
// resolution, it walks an already-typed tuple result (ref/harec/src/
|
||||
// check.c:1354-1419). The module-qualified resolution that makes this
|
||||
// correct for an N_DOT callee lives at the ROOT, in exprtype's N_CALL
|
||||
// arm (the SK_USE-gated scopelookupinmodule there), so the binding just
|
||||
// consumes. A D-class module whose leaf collides with a type/fn name
|
||||
// resolves to nil/wrong-kind at the exprtype root (the SK_USE gate
|
||||
// fails) → no N_TTUPLE → those destructures stay unstamped, a separate
|
||||
// nominal-collision fold (#6a-D), not this one. Annotated bindings keep
|
||||
// their own type. Mirrors the N_FORRANGE binding-install shape above;
|
||||
// the bindings would otherwise install (unstamped) via the generic
|
||||
// N_LET walk, so this early return must register them itself.
|
||||
if (k == nkind.N_MLET) {
|
||||
if (n.rhs != nil) { resolvewalk(c, n.rhs); };
|
||||
let pt: *node = nil;
|
||||
if (n.rhs != nil) { if (n.rhs.kind == nkind.N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) { if (callee.kind == nkind.N_IDENT) {
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
// `rt` would shadow the imported lib/rt module
|
||||
// (checkmoduleshadow errors); `rty` avoids it.
|
||||
let rty: *node = exprtype(c, n.rhs, nil);
|
||||
if (rty != nil) { if (rty.kind == nkind.N_TTUPLE) {
|
||||
pt = rty.list;
|
||||
}; };
|
||||
}; };
|
||||
let l: *node = n.list;
|
||||
@@ -12236,15 +12238,40 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// `foo()` inside module M binds to M.foo rather than another
|
||||
// module's same-leaf foo at the head of the flat scope bucket.
|
||||
// Mirrors cstage cexpr N_IDENT routing through
|
||||
// scope_lookup_prefer with c->cur_mod. N_DOT keeps the bare
|
||||
// scopelookup — its module-qualified resolution is a separate
|
||||
// gap (parser stores the leaf in callee.str; mod is in
|
||||
// callee.lhs.str, not consumed here yet).
|
||||
// scope_lookup_prefer with c->cur_mod.
|
||||
//
|
||||
// #6a-A: a module-qualified `mod.fn()` callee resolves via the
|
||||
// callee.lhs module hint when `mod` is an import (SK_USE) —
|
||||
// scopelookupinmodule(mod, leaf) — mirroring cstage cexpr N_DOT
|
||||
// (cmd/wcc/check.c:1035 scope_lookup_in_module) and cgen's
|
||||
// rettupleof (cgen.ww:2263 fnretlookupmod). Closing this at the
|
||||
// N_CALL root (vs the N_MLET backfill) makes every consumer of a
|
||||
// module-qual call result — destructure binding AND a bare
|
||||
// `mod.fn().0` rvalue — read the right return type via the one
|
||||
// expr path, harec-faithful (binding-unpack does zero callee
|
||||
// resolution, ref/harec/src/check.c:1354-1419). Without it the
|
||||
// bare-leaf scopelookup grabbed whichever same-leaf fn heads the
|
||||
// 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.
|
||||
let s: *sym = nil;
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
s = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
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) {
|
||||
s = scopelookupinmodule(c.cur, callee.lhs.str, nm);
|
||||
} else {
|
||||
s = scopelookup(c.cur, nm);
|
||||
};
|
||||
};
|
||||
if (s == nil) { return nil; };
|
||||
if (s.skind != skind.SK_FN) { return nil; };
|
||||
|
||||
Reference in New Issue
Block a user