Skip to content

fix(jvm): resolve mixed Java/Kotlin callgraph edges via declared packages#818

Open
DeusData wants to merge 2 commits into
mainfrom
distill/684-jvm-callgraph
Open

fix(jvm): resolve mixed Java/Kotlin callgraph edges via declared packages#818
DeusData wants to merge 2 commits into
mainfrom
distill/684-jvm-callgraph

Conversation

@DeusData

@DeusData DeusData commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Distilled from #684 (author: @nguyentamdat) — fixes missing/wrong CALLS edges in mixed Java/Kotlin projects, with one design change decided during review: the new unique-tail fallbacks are gated to JVM callers.

Mixed Gradle/Maven source roots (src/main/java + src/main/kotlin) share JVM packages that path-derived module QNs cannot join. Cross-file LSP emitted package-shaped JVM names while graph nodes stayed path-derived, and same-package defs across roots were dropped by per-file module filtering, so Java ↔ Kotlin calls degraded to weak or wrong textual edges.

Changes

  • Declared package as JVM ground truth (pass_lsp_cross.c): JVM defs are normalized to their declared package via pxc_jvm_def_qn; pxc_infer_jvm_namespace infers the package from conventional source roots only when no declared package was extracted.
  • Namespace index (pass_lsp_cross.c/.h): CBMModuleDefIndex gains a namespace_ht (declared package → defs) alongside the path-module index, so JVM same-package defs survive per-file filtering across source roots. JVM callers are restricted to JVM defs.
  • JVM-gated tail-match fallbacks (lsp_resolve.h) — the design decision on top of fix(jvm): resolve mixed Java/Kotlin callgraph edges #684: the new unique-Class.method-tail fallbacks in cbm_pipeline_find_lsp_resolution and cbm_pipeline_lsp_target_node only fire when the calling file's language is Java or Kotlin (cbm_pipeline_lsp_allow_tail_match). Tail-matching by unique leaf is safe where class-per-file package semantics hold (JVM); in other languages a single wrong-module coincidence would fabricate a CALLS edge. The leaf-key lsp_idx lookup in pass_parallel.c stays global — it aligns the fast path with the build-side key that already used leaf names.
  • Kotlin cross-def signatures (kotlin_lsp.c): cross-file defs register return-type signatures so receiver typing resolves chained Java ↔ Kotlin member calls.
  • JVM cross-LSP skip-pruning bypass (pass_parallel.c): Java/Kotlin files run cross-LSP whenever calls exist — per-file LSP can fill resolved_calls with same-file entries while a mixed-root call remains unresolved.

Tests

  • parallel_java_kotlin_lsp_override_cross_file_emits_lsp_strategy_edges (carried from fix(jvm): resolve mixed Java/Kotlin callgraph edges #684): mixed src/main/java + src/main/kotlin fixture asserts strategy: lsp CALLS edges Java → Kotlin and Kotlin → Java. Red on main (fails at the Java → Kotlin strategy assertion), green with this change.
  • parallel_lsp_tail_match_fallbacks_gated_to_jvm (new, guards the gate): the same wrong-module tail coincidence must resolve with the gate open (JVM) and must NOT with the gate closed. Verified red both when the gate policy is widened and when the in-function guards are bypassed. A natural end-to-end non-JVM coincidence fixture is impractical (reaching the fallbacks requires LSP/extraction QN-prefix drift, which single-root languages don't produce), so the gated branches are exercised directly — noted in the test comment.

Verification

  • make -f Makefile.cbm cbm-Werror clean
  • make -f Makefile.cbm lint-ci — clean
  • ./build/c/test-runner lang_contract parallel go_lsp py_lsp ts_lsp java_lsp kotlin_lsp — 630 passed, 0 failed

DeusData and others added 2 commits July 3, 2026 20:50
…ages

Mixed Gradle/Maven source roots (src/main/java + src/main/kotlin) share
JVM packages that path-derived module QNs cannot join: cross-file LSP
emitted package-shaped names while graph nodes stayed path-derived, and
same-package defs across roots were filtered out, so Java<->Kotlin calls
fell back to weak or wrong textual edges.

The declared package is JVM ground truth. Normalize JVM defs to their
declared package (inferring it from conventional source roots only when
no package was extracted), index defs by declared namespace alongside
the path module so same-package defs survive per-file filtering, restrict
JVM callers to JVM defs, register cross-def return-type signatures for
Kotlin receiver typing, and consume qualified member-call overrides by
leaf name in the parallel lsp_idx fast path.

Distilled from #684 with one design change: the new unique-Class.method
tail-match fallbacks in lsp_resolve.h are gated to JVM callers
(cbm_pipeline_lsp_allow_tail_match, Java/Kotlin only). Tail-matching by
unique leaf is safe where class-per-file package semantics hold; in
other languages a single wrong-module coincidence would fabricate a
CALLS edge. The leaf-key lsp_idx lookup stays global: it aligns the
fast path with the build-side key that already used leaf names.

Tests: carried mixed Java/Kotlin regression (red on main, green here)
plus a gate guard asserting the tail fallbacks stay off for non-JVM
callers and on for JVM.

Co-authored-by: nguyentamdat <nguyentamdat@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData DeusData enabled auto-merge July 3, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant