Repository navigation
fix(cross-repo): point a cross-repo call edge at its call site, not the caller's header - #4669
xiongjianxu wants to merge 1 commit into
Conversation
…he caller's header A cross-repo `calls` edge took its `source_file` from the caller node (an ObjC method node is minted at its `@interface` in the `.h`) while its `source_location` is the call-site line, recorded in the `.m`. Pairing the two pointed `source_file:source_location` into the wrong file — often past the end of a short header. The park payload now carries the call-site file alongside the line, and `link_cross_repo_member_calls` prefers it, falling back to the caller node's file for pre-Graphify-Labs#3152 graphs. The parked path is relativized in the same extract-time pass that relativizes node source_files, so it never leaks the absolute checkout path into graph.json. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Thanks for the pull request, @xiongjianxu. A maintainer will review it soon. Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions. A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic. |
There was a problem hiding this comment.
Graphify reviewed this change.
No issues found by static checks — no coupling regressions or blocking issues in the code graph. That is not the same as safe to merge: see what was not checked.
Not checked: tests were not run; formal verification proved 0 of 3 changed function(s) (3 sampled, not proven).
Formal verification. PR-changed functions: 3/3 verified (0 proven, 3 may-equivalent, 0 distinguished) · 0 not verified.
Graphify review — findings
Fixes the source file on cross-repo member-call edges: they now use the file where the call actually happens (e.g. the .m) instead of the caller node's own file (often the .h declaring the method). Previously the call-site line number was paired with the wrong file, so it could point past the end of an unrelated file. _park_unresolved_member_call now records the call-site file on each parked entry, and extract relativizes it like source_file, so absolute checkout paths don't leak into graph.json or the merged edge. Graphs parked before this change have no file, so link_cross_repo_member_calls falls back to the caller's source_file, which is the old behavior. Adds tests for the new edge source_file, the pre-#3152 fallback, and relative-path parking on ObjC. Existing parked-call assertions in the Go, PHP and TS tests now ignore file, since they check shape rather than location.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 3244 functions depend on the 475 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 989 callers, 59 callees - worse:
link_cross_repo_member_calls()— 34 callers, 10 callees
Verification — 3244 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 2998 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
206 of 437 test file(s) selected (47%) via static blast radius.
tests/test_affected_overrides.py— impacttests/test_astro_extraction.py— impacttests/test_astro_import_ids.py— impacttests/test_blade_extractor.py— impacttests/test_build.py— impacttests/test_builtin_global_type_refs.py— impacttests/test_c_include_resolution.py— impacttests/test_cache.py— impacttests/test_case_sensitive_resolution.py— impacttests/test_cjs_module_extension.py— impacttests/test_cobol_extractor.py— impacttests/test_cpp_method_declarations.py— impacttests/test_cpp_nested_and_cli.py— impacttests/test_cpp_objc_cross_file_calls.py— impacttests/test_cpp_template_type_params.py— impacttests/test_cross_extension_reexport_self_cycle.py— impacttests/test_cross_language_call_resolution.py— impacttests/test_cross_repo_external_call_guards.py— impacttests/test_cross_repo_member_calls.py— impact, changed-testtests/test_csharp_call_site_generic_args.py— impacttests/test_csharp_delegate_declarations.py— impacttests/test_csharp_enum_members.py— impacttests/test_csharp_field_generic_args.py— impacttests/test_csharp_foreach_loop_variable.py— impacttests/test_csharp_generic_callsites.py— impacttests/test_csharp_interface_dispatch.py— impacttests/test_csharp_member_calls.py— impacttests/test_csharp_member_chain_calls.py— impacttests/test_csharp_member_nodes.py— impacttests/test_csharp_object_creation.py— impacttests/test_csharp_partial_classes.py— impacttests/test_csharp_tuple_type_refs.py— impacttests/test_csharp_type_resolution.py— impacttests/test_csharp_underscore_collisions.py— impacttests/test_csharp_var_call_return_type.py— impacttests/test_csharp_verbatim_interpolation.py— impacttests/test_definition_file_portability.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_duplicate_annotation_edges.py— impacttests/test_ejs_extractor.py— impacttests/test_elixir_defdelegate.py— impacttests/test_elixir_import_resolution.py— impacttests/test_elixir_keyword_def_calls.py— impacttests/test_elixir_qualified_calls.py— impacttests/test_elixir_unqualified_call_scope.py— impacttests/test_erlang_extractor.py— impacttests/test_export_direction.py— impacttests/test_ext_tagged_id_clash.py— impacttests/test_extract.py— impact- … and 156 more
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
No difference found (not proven): No behavior difference found in link\_cross\_repo\_member\_calls (not a proof).
The verifier ran both versions of link\_cross\_repo\_member\_calls on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
No difference found (not proven): No behavior difference found in \_park\_unresolved\_member\_call (not a proof).
The verifier ran both versions of \_park\_unresolved\_member\_call on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
No difference found (not proven): No behavior difference found in extract (not a proof).
The verifier ran both versions of extract on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
· 2 more finding(s) on lines outside this diff (see the check run).
Summary
Closes #4668.
A cross-repo
callsedge pointedsource_file:source_locationinto the wrong file.source_filecame from the caller node (an ObjC method node lives at its@interfacein the.h) whilesource_locationis the call-site line (in the.m), so the pair landed past the end of a short header._park_unresolved_member_callnow records the call-sitefilenext toline.source_filerelativization pass relativizes the parkedfiletoo, so it never leaks the absolute checkout path intograph.json(consistent with the parking payload carrying names only, ObjC: #2591 self.<field> receiver typing emits zero edges via the CLI — objc_field_types keys are skipped by the #1529 id remap #3150).link_cross_repo_member_callsprefers the parkedfile, falling back to the caller node'ssource_filefor graphs built before the field existed.Testing
tests/test_parked_call_site_file.py: the parkedfileis the repo-relative call-site.m, never absolute.source_fileis the call site, and a pre-merge-graphs / global add can never produce cross-repo member-call edges: the resolved receiver type is discarded at bail-out instead of persisted #3152 entry withoutfilefalls back to the caller's file.filekey (its exact value depends on a root those unit cases extract without).pytest -k "objc or cross_repo or parked or receiver"green apart from 7 failures intest_ext_tagged_id_clash.pythat are pre-existing onv8(File node takes the label of a module-level variable when a same-stem sibling file exists (x.mjs + x.sql, const sql) #4449), unrelated to this change.ruffclean.Based on
v8.