fix(parsers/python): resolve self/cls-aliased method calls in the call graph#115
fix(parsers/python): resolve self/cls-aliased method calls in the call graph#115gadievron wants to merge 1 commit into
Conversation
|
Merge-order note (not a defect — flagging for landing order) This is an explicit follow-up to #112 (same |
…l graph `_resolve_call_node` matched a method-call receiver only when it was literally named `self`/`cls`, so an aliased receiver — `obj = self; obj.method()` or `alias = cls; alias.method()` in a classmethod — produced NO call-graph edge. The inherited/self method then looked unreachable (a false negative in the reachable set, the dangerous direction for reachability-based analysis). Fix: in `_extract_calls_from_code`, collect local names single-bound to `self`/`cls` (`_collect_self_aliases`) and pass them to `_resolve_call_node`, which now routes any `<alias>.method()` through `_resolve_self_call`. Only single, unconditional bindings count — a name reassigned to anything else is not treated as an alias, so no spurious edge is created. The change only ADDS previously-dropped edges; it never removes one (raises reachability, never lowers it) and is local to the receiver-resolution branch (no architecture change). Tests (tests/parsers/python/test_call_graph_self_calls.py): RED before / GREEN after — `test_self_alias_method_call_edge`, `test_cls_alias_method_call_edge`, plus `test_reassigned_alias_does_not_force_self_edge` (soundness guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
c26c4e3 to
2f005fa
Compare
|
Rebased onto origin/master (post-release #133 / Wave 1). Conflicts resolved by UNION — both this PR's fix and the Wave-1 parser fixes are preserved; no Wave-1 change reverted (independently judge-verified). Force-pushed with --force-with-lease. Python parser tests green (14 + 7). Real union onto master (kept master local_types + this PR self/cls aliases). |
|
Closing as already resolved on current master. This fix's behavior shipped via the parser-fix-stack release (#133/#134); confirmed by independent reproduction — the original bug (reconstructed from its bug-pipeline investigation) no longer manifests on master, not merely by line-presence. Part of a full validated sweep of the open-PR corpus. |
Root cause
CallGraphBuilder._resolve_call_node(libs/openant-core/parsers/python/call_graph_builder.py) matched a method-call receiver only when it was the literal nameselforcls. A receiver that is a local alias of self/cls —obj = self; obj.method(), oralias = cls; alias.method()inside a classmethod — fell through to_resolve_module_call, which finds no import/class for a plain local variable and returnsNone. The self/class-method call produced no call-graph edge, so the callee can look unreachable (a false negative in the reachable set).Reproduction
Before:
call_graph["m.py:C.caller"]is[](theC.caller -> C.targetedge is missing).How
_collect_self_aliases(tree): scans the function body for local names single-bound toself/cls(a name assigned more than once, or ever rebound to anything else, is not treated as an alias — so no spurious edge)._extract_calls_from_codebuildsself_aliases = {'self','cls'} | _collect_self_aliases(tree)and passes it to_resolve_call_node._resolve_call_nodetakesself_aliases(defaulting to{'self','cls'}so existing callers are unchanged) and resolves any<alias>.method()through the existing_resolve_self_call. The two priorself/clsbranches collapse into oneobj.id in self_aliasescheck.This only adds edges that were previously dropped; it never removes one — so it raises reachability, never lowers it. The change is confined to the receiver-resolution branch (no new subsystem / data model).
Regression test
libs/openant-core/tests/parsers/python/test_call_graph_self_calls.py(+62 lines):test_self_alias_method_call_edge—obj = self; obj.target()edge present.test_cls_alias_method_call_edge—alias = cls; alias.target()edge present.test_reassigned_alias_does_not_force_self_edge— soundness guard: a name reassigned away from self is NOT aliased.RED (before fix):
GREEN (after fix), full
tests/parsers/python/:Compatibility
None.
_resolve_call_node's new parameter is optional and defaults to the previous behavior. No public API change.Notes
The canonical per-parser
tests/parsers/python/test_callgraph_symmetry.py/test_python_schema_completeness.pydo not yet exist in this repo (a pre-existing gap across all parsers, not introduced here); this fix extends the existingtest_call_graph_self_calls.py.Author notes
_collect_self_aliases(new), theobj.id in self_aliasesreceiver branch, and theself_aliasesthread-through in_extract_calls_from_code.obj = self; obj.target()— previously noC.caller -> C.targetedge; now resolved.= self/= clsbindings count, pinned bytest_reassigned_alias_does_not_force_self_edge.Coordination with open PRs
#112 adds a
_build_alias_mapto the same resolution path, but for function-value aliases (fn = helper; fn()) — its docstring notes class/method targets are out of scope. This PR handles the distinct self/cls alias case (obj = self; obj.method()), which #112 leaves unresolved. The two are complementary but touch the same code; they should be unified rather than stacked. Happy to rebase onto #112 and fold the self/cls aliasing into its alias map.