Conversation
A record's accessors are implicit, so `record Point(double x, double y, double z)` declares no `x()` and extraction emitted no node for one. The call `p.x()` had nothing to resolve to, fell through to the short-name registry, and bound to the first `x` in the project — typically an unrelated class's private field. Measured on two indexed Java trees, that was 278 of 2444 and 2649 of 29509 CALLS edges. Extraction now emits one Method definition per record component, skipping any accessor the record declares itself (JLS 8.10.3), so the accessor has a node and the call resolves to it. And a Java call may no longer bind to a Variable or a Field. A Java call expression never names data, so such an edge is a spelling collision, not a call. The guard follows cbm_go_suppress_bare_field_ref — a pure, label-keyed predicate — and is called from both pass_calls.c and pass_parallel.c so the sequential and parallel resolvers stay identical. Java-gated: Kotlin properties hold function values and C has function pointers, so both legitimately call through data. Signed-off-by: MopicMP <obshiq123@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
|
Written by an AI agent (Claude Code) working for @MopicMP. The only red here is Of the last 61 PR-workflow runs that ran this guard, it was green 56 times and red 5. All five reds are that same section, and four of them are on branches that cannot reach it:
Everything else is green: all nine Could a maintainer re-run that job? I have no permission to re-run it from here. Happy to rebase instead if you would rather see a fresh run of the whole leg. |
|
Thanks for the careful flake analysis, @MopicMP, and sorry for the wait. You're right that |
Written by an AI agent (Claude Code) working for @MopicMP, who signed off the
commit and answers for it.
A Java record accessor call binds to an unrelated field
p.x()on a record ends up as aCALLSedge into some other class's privatefield named
x.A record's accessors are implicit, so
record Point(double x, double y, double z)declares nox()anywhere in the source, and extraction emits no node forone. The call has nothing to resolve to, falls through to
cbm_registry_resolve,and the short-name registry hands back the first
xin the project — typicallya private field of a class that has nothing to do with the receiver.
Two answers invert:
deleted a live method on that answer;
How much of the graph this is
Two indexed Java trees, v0.10.8 release binary,
mode=fast, Windows 11 x86_64:CALLSedgesVariableorFieldCounted with
MATCH (a)-[:CALLS]->(b) WITH b.label AS kind, count(*) AS n RETURN kind, n.moderateandfullresolve the same way, and so does thev0.11.0 release binary.
The change, in two parts
Extraction emits the accessors (
internal/cbm/extract_defs.c). OneMethoddefinition per record component, skipped when the record declares that accessor
itself (JLS §8.10.3 allows the override). The record's own API becomes visible:
the accessor gets a node, and
p.x()resolves to it.A Java call may not bind to data (
src/pipeline/registry.c, both callsites). A Java call expression never names data —
p.x()is a methodinvocation whatever
xspells elsewhere in the tree — so aCALLSbind whosetarget is a
Variableor aFieldis a spelling collision and is dropped.This follows
cbm_go_suppress_bare_field_ref: a pure, label-keyed,language-gated predicate, called from
pass_calls.candpass_parallel.csothe sequential and parallel resolvers stay identical.
Java-gated deliberately: the veto is sound only where no callable name can be
anything but a method. Kotlin properties hold function values (
val f: () -> Unit; f()) and C has function pointers, so both legitimately call through data.Both halves are needed. The first makes the right target exist; the second
stops the wrong one being written where the right one is not reachable.
Measured
On the 631-file tree, indexed with this build: 2 103
CALLSedges, every oneinto a
Method(1 841) or aClass(262). NoVariable, noField— downfrom 278.
A separate gap this uncovered, not fixed here
Java cross-file type resolution appears to stop working when the project has
more than one source root. Same four files, two layouts:
src/com/x/...):where.x()andwhere.z()bind toPoint.x/Point.zacross files and across packages, with an unrelatedSpring.xfield present;
src/main/java/...plussrc/client/java/..., the Gradle/Fabricshape): the same calls do not bind at all. A hand-written
public double x()in the record does not bind either, so it is not about the implicit accessor
— everything that binds in that layout binds by short name.
I have a four-file reproduction and can open a separate issue for it; it looked
like a different defect from this one, so I kept it out of this PR.
Tests
tests/test_registry.c—java_call_never_binds_data_member: the predicate,including the keeps (Method/Function/Class, other languages, NULL).
tests/test_call_reference_contract.c—call_java_record_accessor_binds_the_record: end to end through the indexingpath. The accessor call binds to the record; no
CALLSinto the unrelatedfield; a builder method
x(int)declared beside its ownxfield keeps itsedge; the genuine static call in the same method still lands.
Built and run with gcc 15.2.0 on Debian (WSL2):
scripts/test.sh, plusclang-formatclean on every changed file.