You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If you call a method with the scoped/static syntax (Mailer::send($x) in PHP, Mailer::send(x) in Rust), the call edge gets stored but callers_of, get_impact_radius, and tests_for never see it. They report zero callers for a method that's obviously being called.
The data isn't lost, it's just filed under a key nothing looks up. The parser records the CALLS edge with the target Mailer::send, but every node in the graph is keyed by its dotted qualified name, e.g. src/Mailer.php::Mailer.send. Those two strings never compare equal, and nothing in the resolution layer bridges them, so the edge dangles.
The underlying reason: the resolution layer assumes a call target is one of two shapes, a fully-qualified node name (<file>::Class.method) or a bare name (method). The Class::method form is neither, so it falls through every path.
This is on 2.3.6. I'd call it high severity. It's a common call style in general, and on Rust it's the normal way to call associated functions and constructors (Foo::new(), Self::method()), so a big chunk of Rust call edges quietly go missing, which makes blast-radius and review scope under-report.
Which languages this actually affects
I built a small repo for each ::-using language and looked at the edges table directly. Only PHP and Rust actually hit this:
PHPMailer::send($x) — stores Mailer::send, dangles against …::Mailer.send. Affected.
Ruby — same as C++, no CALLS edge produced for the scoped call.
Rpkg::fn(x) — does produce pkg::fn, but pkg is an external package with no node in the graph, so there's nothing to resolve it to. Intra-file calls already resolve fine.
So this report is scoped to PHP and Rust, where the edge exists but points at the wrong key. C++/Ruby/R are out of scope here for the reasons above.
You'd expect SignupController.register to show up as a caller of Mailer.send. Instead you get nothing.
The edge is in the DB, just keyed wrong:
sqlite>SELECT source_qualified, target_qualified FROM edges
WHERE kind='CALLS'AND target_qualified LIKE'%send%';
src/SignupController.php::SignupController.register | Mailer::send -- target is "Class::method"
sqlite>SELECT qualified_name FROM nodes WHERE name='send';
src/Mailer.php::Mailer.send-- node key is "<file>::Class.method"
The same two-file pattern in Rust (struct Mailer; impl Mailer { fn send(..) }, called via Mailer::send(..)) reproduces identically.
If you poke at the store directly, it's clearly the resolution layer and not extraction:
store.resolve_bare_call_targets() -> 0 # batch resolver never touches it (it skips '::' targets)
store.search_edges_by_target_name("send") -> 0 # the callers_of bare-name fallback misses it
store.search_edges_by_target_name("Mailer::send") -> 1 # only findable by the Class::method key, which nothing queries
store.get_edges_by_target("src/Mailer.php::Mailer.send") -> 1 CONTAINS edge only (no CALLS)
Where it goes wrong
Three spots all make the same assumption (line numbers from main @ 2.3.6, they'll drift):
code_review_graph/parser.py:2189 (_resolve_call_targets) only qualifies bare targets:
code_review_graph/graph.py:499 (resolve_bare_call_targets) explicitly excludes anything with :::
"FROM edges WHERE kind = 'CALLS' AND target_qualified NOT LIKE '%::%'"
code_review_graph/graph.py:370 (search_edges_by_target_name, the cross-file callers_of fallback) matches on the bare name:
"SELECT * FROM edges WHERE target_qualified = ? AND kind = ?", (name, kind)
The stored target is Mailer::send, the lookup is send, no match.
The target itself is produced in the PHP scoped-call handler at parser.py:6462, which returns f"{parts[0]}::{parts[-1]}". Worth noting: Rust's scoped_identifier (parser.py:6567) and R's namespace_operator (parser.py:6571) return the full scoped text, so a Rust target can carry more than one :: (e.g. crate::module::func).
Environment
code-review-graph 2.3.6
Reproduced on PHP and Rust
Checked and ruled out: C++ and Ruby (no CALLS edge produced, separate extraction issue) and R (:: is external package access, no in-graph target)
What's wrong
If you call a method with the scoped/static syntax (
Mailer::send($x)in PHP,Mailer::send(x)in Rust), the call edge gets stored butcallers_of,get_impact_radius, andtests_fornever see it. They report zero callers for a method that's obviously being called.The data isn't lost, it's just filed under a key nothing looks up. The parser records the CALLS edge with the target
Mailer::send, but every node in the graph is keyed by its dotted qualified name, e.g.src/Mailer.php::Mailer.send. Those two strings never compare equal, and nothing in the resolution layer bridges them, so the edge dangles.The underlying reason: the resolution layer assumes a call target is one of two shapes, a fully-qualified node name (
<file>::Class.method) or a bare name (method). TheClass::methodform is neither, so it falls through every path.This is on 2.3.6. I'd call it high severity. It's a common call style in general, and on Rust it's the normal way to call associated functions and constructors (
Foo::new(),Self::method()), so a big chunk of Rust call edges quietly go missing, which makes blast-radius and review scope under-report.Which languages this actually affects
I built a small repo for each
::-using language and looked at theedgestable directly. Only PHP and Rust actually hit this:Mailer::send($x)— storesMailer::send, dangles against…::Mailer.send. Affected.Mailer::send(x)— same thing. Affected.Mailer::send(x)— no CALLS edge gets created at all, so there's nothing to dangle. That's a different, extraction-side problem (looks adjacent to C++ class method definitions not parsed as Function nodes (scopedClass::method()and Qt macros) #463).pkg::fn(x)— does producepkg::fn, butpkgis an external package with no node in the graph, so there's nothing to resolve it to. Intra-file calls already resolve fine.So this report is scoped to PHP and Rust, where the edge exists but points at the wrong key. C++/Ruby/R are out of scope here for the reasons above.
Reproduction
Two PHP files:
src/Mailer.phpsrc/SignupController.phpBuild it and query:
code-review-graph build --repo .You'd expect
SignupController.registerto show up as a caller ofMailer.send. Instead you get nothing.The edge is in the DB, just keyed wrong:
The same two-file pattern in Rust (
struct Mailer; impl Mailer { fn send(..) }, called viaMailer::send(..)) reproduces identically.If you poke at the store directly, it's clearly the resolution layer and not extraction:
Where it goes wrong
Three spots all make the same assumption (line numbers from
main@ 2.3.6, they'll drift):code_review_graph/parser.py:2189(_resolve_call_targets) only qualifies bare targets:Class::methodhas::, so it's skipped.code_review_graph/graph.py:499(resolve_bare_call_targets) explicitly excludes anything with:::"FROM edges WHERE kind = 'CALLS' AND target_qualified NOT LIKE '%::%'"code_review_graph/graph.py:370(search_edges_by_target_name, the cross-filecallers_offallback) matches on the bare name:The stored target is
Mailer::send, the lookup issend, no match.The target itself is produced in the PHP scoped-call handler at
parser.py:6462, which returnsf"{parts[0]}::{parts[-1]}". Worth noting: Rust'sscoped_identifier(parser.py:6567) and R'snamespace_operator(parser.py:6571) return the full scoped text, so a Rust target can carry more than one::(e.g.crate::module::func).Environment
::is external package access, no in-graph target)