Repository navigation
[Bug]: An unqualified call in Java, C#, Scala and C++ is bound to the last same-named method of the file, not the one in the caller's class #4243
Description
Pre-flight checks
- I have checked the Troubleshooting section in the README
What happened?
Inside a method, a call written b() gets a calls edge to whichever method named b the file declares last, whatever class it belongs to, and the edge is EXTRACTED. With a nested Inner.b() declared after Outer.b(), the b() in Outer.a() points at Inner.b(). Declare Inner first and the edge is right, so the result depends on declaration order. A later unrelated class that repeats the name captures the call the same way.
On a real project: in Guava's Synchronized.java, 195 of the 213 delegate() call edges point at SynchronizedTable.delegate, the class declared last, instead of the delegate() of their own class.
Expected: the b of the nearest class that declares one: the caller's own class and its in-file bases, then the classes around it.
Cause, in graphify/extractors/engine.py: for these languages the unqualified call falls to tgt_nid = label_to_nid.get(callee_name) in walk_calls, and label_to_nid[normalised] = n["id"] is overwritten by each node with the same label. The same defect was fixed for self/this calls in Python, JS/TS, Swift and Ruby (_self_call_target: e519288, 60d905a, c470cb5, 74fa510); PHP joined that set in d2b5e19. method_owner and methods_by_owner are only built for the languages of _SELF_CALL_LANGUAGES.
Steps to reproduce
mkdir demo && cd demo
cat > Outer.java <<'EOF'
public class Outer {
void a() { b(); }
void b() {}
static class Inner {
void b() {}
}
}
EOF
cat > Outer.cs <<'EOF'
public class Outer
{
public void A() { B(); }
public void B() {}
public class Inner
{
public void B() {}
}
}
EOF
cat > Outer.scala <<'EOF'
class Outer {
def a(): Unit = b()
def b(): Unit = ()
class Inner {
def b(): Unit = ()
}
}
EOF
cat > Outer.cpp <<'EOF'
class Outer {
public:
void a() { b(); }
void b() {}
class Inner {
public:
void b() {}
};
};
EOF
graphify update .
python - <<'EOF'
import json
g = json.load(open("graphify-out/graph.json"))
nodes = {n["id"]: n for n in g["nodes"]}
links = g.get("links") or g.get("edges")
owner = {e["target"]: e["source"] for e in links if e.get("relation") == "method"}
for e in links:
if e.get("relation") == "calls" and nodes[e["source"]]["label"].strip(".()").lower() == "a":
print(nodes[e["source"]]["source_file"], "Outer.a() ->", nodes[owner[e["target"]]]["label"] + "." + nodes[e["target"]]["label"].strip(".()") + "()")
EOFError output or graph output
Outer.cpp Outer.a() -> Inner.b()
Outer.cs Outer.a() -> Inner.B()
Outer.java Outer.a() -> Inner.b()
Outer.scala Outer.a() -> Inner.b()
All four should read Outer.b() (Outer.B() in C#).
Graphify version
0.9.80 (v8 @ 6478eb7)
Operating System
macOS
Python Version
3.12
Installation Method
built from source (git clone), uv sync --all-extras
Additional Environment Details
AST-only path (graphify update), no API key involved.
Additional context
I have a fix with tests and can open a PR (three commits: the caller's own class, its in-file bases, the classes around it). An unqualified call looks in the caller's class and its in-file bases (also a base declared further down the file, and the type after the new of an anonymous class), then in each class around it (an anonymous class steps out through the method that holds it), and keeps the file-wide lookup when no class on that path declares the name. Kotlin is not part of it: Kotlin falls back to a top-level function when a member's parameters do not fit the call, which a name-only lookup cannot tell.
Related: #2041 (umbrella), point 4: bare calls resolve by label text. This issue is limited to unqualified calls made from inside a class in Java, C#, Scala and C++; calls with a receiver are not touched.
Activity
github-actions commentedon Oct 8, 2026
Thanks for opening this issue, @Yyunozor. A maintainer will take a look soon.
If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.
To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.
Yyunozor commentedon Oct 8, 2026
PR opened: #4252
safishamsi commentedon Oct 9, 2026
Fixed by #4252 (thanks @Yyunozor), shipped in v0.9.83. Closing as resolved.