fix(extract): never bind a cross-file call to a definition in another language family#1718
Closed
edinaldoof wants to merge 1 commit into
Closed
fix(extract): never bind a cross-file call to a definition in another language family#1718edinaldoof wants to merge 1 commit into
edinaldoof wants to merge 1 commit into
Conversation
… language family
The cross-file call resolver matches raw-call callees against a repo-wide
label index with no language check. In a repo that mixes a web app with a
native Android app, a TSX callback passed by name (register(refreshHeading))
resolved to a same-named Kotlin method and shipped as an INFERRED
indirect_call edge — a phantom the extraction spec itself forbids ('calls
edges MUST stay within one language'). Direct calls from non-JS/TS callers
had the same hole with no gate at all: a bare Python call bound to the lone
same-named Kotlin fun. Found on a production Next.js + Android codebase,
where the phantom edge also inflated the Kotlin node's betweenness enough
to surface it as a top suggested question in GRAPH_REPORT.md.
Resolution candidates are now filtered by language interop family before
the single-candidate/import-evidence logic runs. Families are grouped by
REAL interop so legitimate cross-language resolution keeps working:
Kotlin/Java/Scala/Groovy share the JVM, C/C++/Objective-C/CUDA share
headers (Swift bridges to ObjC), and JS/TS variants plus Vue/Svelte/Astro
SFCs compile into one module graph. Candidates whose family is unknown
(no source_file, non-code nodes) are never filtered, preserving the
previous permissive behavior, and callers with an unmapped extension skip
the guard entirely.
Collaborator
|
Merged into |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The cross-file call resolver matches raw-call callees against a repo-wide label index with no language check. In a repo that mixes a web app with a native Android app (a common layout: Next.js/React + a Kotlin WebView shell), this manufactures phantom cross-language call edges — which the extraction spec itself forbids ("
callsedges MUST stay within one language: a Python function cannotcallsa JS/TS/Go/Rust/Java symbol and vice versa — cross-language call edges are phantom artifacts, never emit them"). The local AST resolver never enforced that rule.Minimal repro:
Current output:
A TSX component "calling" a Kotlin method. The #1659 import-evidence gate doesn't apply (it covers JS/TS direct calls only), and the indirect path's callable-target guard passes because the Kotlin method is a real callable — just in the wrong language.
Direct calls from non-JS/TS callers have the same hole with no gate at all:
Beyond the wrong edge itself, the damage compounds downstream: on the codebase where I hit this, the phantom edge bridged the Android community and the web-navigation community, inflating the Kotlin node's betweenness centrality enough that GRAPH_REPORT.md surfaced it as a top suggested question ("Why does this node connect community X to communities Y, Z?") — an artifact presented as an insight.
Fix
Filter resolution candidates by language interop family right after the label-index lookup, before the single-candidate/import-evidence logic runs. Families are grouped by real interop so legitimate cross-language resolution keeps working:
jsts: .js/.jsx/.mjs/.cjs/.ts/.tsx/.mts/.cts + .vue/.svelte/.astro (one module graph)jvm: .java/.kt/.kts/.scala/.groovy/.gradle (JVM interop — a Kotlin call to a Java method still resolves)native: .c/.h/.cpp/.cc/.cxx/.hpp/.cu/.cuh/.metal/.m/.mm/.swift (shared headers, ObjC/C++ mixing, Swift↔ObjC bridging)Two deliberate permissive defaults, preserving existing behavior everywhere the guard has no opinion:
source_file, non-code nodes) are never filteredThis covers both the
indirect_callpath and the direct-call path (the same candidates list feeds both), and can only ever narrow candidates — so an ambiguous 2-candidate name split across languages now collapses to the right-language definition instead of hitting the tie-breakers.Tests
tests/test_cross_language_call_resolution.py(mirrors thetest_phantom_cross_package_call.pystructure):test_tsx_callback_does_not_bind_to_kotlin_method— the repro abovetest_python_call_does_not_bind_to_kotlin_function— direct-call path for non-JS/TS callerstest_same_language_callback_still_resolves— positive control: TS callback with import evidence keeps its INFERREDindirect_calltest_jvm_interop_kotlin_call_to_java_still_resolves— positive control: Kotlin→Java keeps resolving (verified this passes onmaintoo, so the family grouping changes nothing for JVM interop)Full suite: 2986 passed; the handful of failures on my machine (
test_skillgen,test_hooks,test_ollama_retry_cap,test_detect::test_graphifyignore_hermetic_without_vcs) fail identically on a clean checkout ofmain— environment-related, untouched by this change.