Java: a method reference (Type::method) is not a call site for --uses/--callers
- Dominant language
- C++
- Stars
- 2.1k
- Forks
- 125
- Avg merge
- 5h 28m
- Merged PRs (30d)
- 163
Description
### What happens
Two functionally identical Java methods, one written with a lambda and one with a method reference:
```bash
mkdir rw-mr && cd rw-mr
```
```java
cat > Util.java <<'EOF'
public class Util {
public static String conv(Object o) { return String.valueOf(o); }
}
EOF
```
```java
cat > A.java <<'EOF'
import java.util.List;
public class A {
public List lambdaForm(List in) {
return in.stream().map(item -> Util.conv(item)).toList();
}
public List methodRefForm(List in) {
return in.stream().map(Util::conv).toList();
}
}
EOF
```
```bash
ripwire . --no-cache --uses=conv --format=rows
ripwire . --no-cache --callers=conv --json
```
--uses finds one call site out of two — only the lambda, at line 5:
```xml
0=A.java
05calllambdaForm
```
--callers likewise lists only lambdaForm; methodRefForm does not appear:
```json
{"of":"conv","defs":1,"count":1,"root":".","hop_tested":0,"hop_untested":1,
"graph_ambiguous":0,"graph_unresolved":0,"counts_floor":true,
"callers":[{"t":"method","n":"lambdaForm","p":"A.java:4"}]}
```
Environment: ripwire 0.5.0 (Release, GNU 14.2.1, built_from=unknown), Ubuntu 24.04.4 LTS x86_64, each arm in a fresh directory with --no-cache.
### Why I'm reporting it rather than reading it as the documented callback caveat
--callers' legend lists what name-based extraction cannot see: dynamic dispatch, and callbacks bound to more than one function in scope. Util::conv is neither. The receiver is a type name and the member is a literal identifier in the source text, so the target is fixed at compile time — as fixed as the Util.conv(item) three lines above it, which does produce an edge. Delete conv and both forms stop compiling; only one of the two is visible to ripwire.
I'd draw the line at the type receiver: instance::method on an expression, or a reference passed around and bound elsewhere, is a genuine callback and I'd expect it to stay unresolved. It's the Type::method form that reads as a real, statically known dependency.
### Why it matters in practice
The method reference is the preferred spelling in Java generally — Effective Java item 43 says to prefer it over the equivalent lambda, and both IntelliJ ("Lambda can be replaced with method reference") and Sonar (java:S1612) flag .map(x -> Foo.bar(x)) and suggest rewriting it as .map(Foo::bar). On the codebase where I hit this, one utility method had four call sites and two of them were method references: --uses reported half the truth. The more consistently a codebase follows the idiom, the thinner its call graph gets, and since these are missing edges rather than skipped files, nothing in the output marks the gap — counts_floor="1" covers it formally, but the reader has no way to tell this floor from a complete answer.
Contributor guide
Research direction
Reproduce the discrepancy with Util.java and A.java using the --uses=conv and --callers=conv commands shown in the issue. Trace the Java call-site extraction entry point for Type::method references and compare it with the lambda result. Done means both the lambda and Util::conv method reference appear in the reported uses and callers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, java
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100