@@ -975,18 +975,20 @@ func (w *pyWalker) emitCollectionValueRefs(node *sitter.Node) {
975975// be wrong. Only imported names and same-class methods — which are unambiguously
976976// real symbol references — resolve.
977977func (w * pyWalker ) valueRefTarget (name string ) string {
978+ // A name bound in the enclosing function's own scope — a param or a local
979+ // assigned/iterated/aliased name — shadows a same-class method or same-module def
980+ // (e.g. self.x = x in __init__ passing the param, not the same-named property).
981+ if w .localBound [name ] {
982+ return ""
983+ }
978984 if methods := w .currentMethods (); methods [name ] {
979985 return w .module + "." + w .enclosingType () + "." + name
980986 }
981987 if t , ok := w .importMap [name ]; ok && t != "" {
982988 return t
983989 }
984- // Same-module top-level def (function or class) referenced by name. Skip when the
985- // name is bound in the enclosing function's own scope — a param or a
986- // local assigned/iterated/aliased name (it shadows the def — e.g.
987- // get_user(user_id) passing the param, not the same-named function). Rescues
988- // `f(local_helper)` where local_helper is a same-module def.
989- if w .idx != nil && ! w .localBound [name ] && w .idx .moduleDefs [w .module ][name ] {
990+ // Same-module top-level def (function or class) referenced by name.
991+ if w .idx != nil && w .idx .moduleDefs [w .module ][name ] {
990992 return w .module + "." + name
991993 }
992994 return ""
@@ -1484,6 +1486,12 @@ func (w *pyWalker) emitImplementorCalls(owner *facts.Fact, methodName, qualType
14841486
14851487// resolveCall maps a bare call name to a canonical fact target.
14861488func (w * pyWalker ) resolveCall (name string ) string {
1489+ // A bare name that shadows a param/local/loop-var is that local binding, not a
1490+ // same-class method or module-level def (e.g. def wrapper(cb): cb()), so this must
1491+ // gate every branch below, not just the same-module fallback.
1492+ if w .localBound [name ] {
1493+ return ""
1494+ }
14871495 // Same-class method.
14881496 if methods := w .currentMethods (); methods [name ] {
14891497 return w .module + "." + w .enclosingType () + "." + name
@@ -1492,14 +1500,10 @@ func (w *pyWalker) resolveCall(name string) string {
14921500 if target , ok := w .importMap [name ]; ok {
14931501 return target // "" means external → no edge
14941502 }
1495- // Same-module top-level function. A bare callee that shadows a param/local/loop-var
1496- // is that local binding, not the module-level def (e.g. def wrapper(cb): cb()). When
1497- // an index is available, resolve only names that are actually module-level defs, so
1498- // callable locals/params/loop vars don't fabricate edges. Without an index (single-file
1499- // extraction) fall back to best-effort; production always supplies one.
1500- if w .localBound [name ] {
1501- return ""
1502- }
1503+ // Same-module top-level function. When an index is available, resolve only names
1504+ // that are actually module-level defs, so callable locals/params/loop vars don't
1505+ // fabricate edges. Without an index (single-file extraction) fall back to
1506+ // best-effort; production always supplies one.
15031507 if w .idx != nil {
15041508 if w .idx .moduleDefs [w .module ][name ] {
15051509 return w .module + "." + name
0 commit comments