Repository navigation
fix(jvm): a call through an import reaches the Java or Kotlin class it names - #3023
Merged
Merged
Conversation
…t names A Java or Kotlin import was matched against a file path written with the calling file's own extension (com/example/Foo.java from Java, .kt from Kotlin), so an import across the two languages never bound: Kotlin's JUtil.make() on an imported Java class went to a same-named class in another package, JUtil.Nested.deep() and Outer.Inner() linked nothing, and Java's KUtil.INSTANCE.kmake() went to another package's object. The import is now read by qualified name in either language (the package is some prefix of the name, so import p.Outer.Inner is p::Outer::Inner), and the rest of the reference walks the imported type's nested types: a Kotlin object's INSTANCE and a class's Companion, as Java writes them, step onto the type, whose companion members the index keeps on the class. Java's UtilsKt.helper() reaches the top-level function of p/Utils.kt, and import p.UtilsKt links that file. A call picks the member's method over a same-named field or nested class (jextract's Index_h.clang_getRange() is the static method, not the holder class of that name), the overload its argument count fits (Minestom's NetworkBufferTemplate.template(a, g, b, h, ctor)), and, from Kotlin, a nested Java class's own constructor. A Kotlin chain rooted at an imported type is walked before the receiver-chain typing. Only an imported type takes this path; an imported function or property is left to the name strategies, which know a call's receiver. A Kotlin extension declared in another package (fun Modes.hidden(), indexed as Modes::hidden) is now a candidate only from a file that imports it or its package, as Kotlin has it. Validation: - jvm-cross-language-imports.test.ts: 8 tests, all red on main, green here (cases from #1948's test and the T5 triage fixtures). - A/B against main 86c85cf (the base, 8bd55fa, gives identical graphs on jextract and nowinandroid, as 4955eac did there and on okhttp), sites added/removed/retargeted, with confidence raised at the same target: okhttp +172/-0/30 (167 raised), SystemUI +83/-0/25 (642), mihon +21/-26/28, nowinandroid +24/-0/5, jextract +96/-96/18 (calls that were instantiations of a same-named holder class now call the static method), spring-modulith 0/0/16, Minestom 0/0/843 (799 onto the overload their argument count fits, none against it), spring-ai +14/-12/73 (50 overloads; 22 onto the imported provider's nested class), playframework 0/0/37, kestra 0/0/302 (290 overloads); click and gin identical. Added: Java calls into Kotlin companions and objects (SystemUI's PhysicsAnimator.getInstance, okhttp's RequestBody.create), Kotlin calls into Java classes, type references across the two. Removed: guesses onto extensions a file cannot see (mihon's JsonObject.toByteArray on a String) and calls that become instantiations of a nested type. - Cost (3 interleaved fresh indexes): mihon CPU 27.7 s on main vs 27.5 s, wall 8.6 vs 8.7 s; okhttp CPU 31.5 s on both, wall 10.0 vs 9.8 s. Reimplements #1948 by @mixxer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inth3shadows
added a commit
to inth3shadows/codegraph
that referenced
this pull request
Oct 29, 2026
Brings 4955eac..787eafb (colbymchenry#3014, colbymchenry#3017, colbymchenry#3020, colbymchenry#3018, colbymchenry#3023). Clean auto-merge. README: drop colbymchenry#2871 (closed upstream; colbymchenry#2934 merged in its place, and name-matcher already matches upstream).
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.
Summary
A Java or Kotlin call through an import now reaches the class the import names, whichever of the two languages declares it: Kotlin's
JUtil.make(),JUtil.Nested.deep()andOuter.Inner()on an imported Java class, and Java'sKUtil.INSTANCE.kmake()on a Kotlin object,Factory.Companion.create()on a companion orUtilsKt.helper()on a Kotlin file's top-level function. A Kotlin extension declared in another package is now in scope only through an import of it or of its package.Root cause
resolveJavaImportedReferencematched an import against a file path built with the calling file's own extension (com/example/Foo.javafrom a Java file,.ktfrom a Kotlin one) and only took declarations in the caller's language, so an import across the two languages never bound. The call then fell to the name strategies: Kotlin'sJUtil.make()went to a same-namedother.JUtil, and Java'sKUtil.INSTANCE.kmake()to another package's object. Nested types (JUtil.Nested.deep()),INSTANCE,CompanionandFileKtwere never walked at all. Separately, the instance-method and class-name strategies took a Kotlin extension (fun Modes.hidden(), indexed asModes::hidden) as a member ofModesfrom any file, imported or not.What changed (our implementation)
import p.Outer.Innerisp::Outer::Inner); several declarations of the name (Kotlin Multiplatform) go to the closest. The rest of the reference walks the imported type's nested types;INSTANCEandCompanionstep onto the type, whose companion members the index keeps on the class. A call takes the member's method over a same-named field or nested class, the overload its argument count fits (the existingretargetSelfOverload), and from Kotlin a nested Java class's own constructor (Outer.Inner()), else the class. From Java,p.UtilsKtis the facade ofp/Utils.kt: its top-level functions and properties, and the import links the file.x.toImmutableList()) is no call of an imported top-level function of that name, and a name that is a function and a type at once (Compose'sfun AppBar(…)besidesealed interface AppBar) is called as the function.JUtil.Nested.deep()) is walked through the import before the receiver-chain typing, which only knows Kotlin types.isKotlinExtensionInScope(name-matcher) drops an extension the calling file cannot see fromresolveMethodOnType, the class-name strategies and the one-method guess, using the existingisKotlinTopLevelVisible; a member of a class in its file's package always passes.Reimplements #1948 by @mixxer, independently: the PR's diff and test were read, not run or merged. From its test we reused the cases (
nested,unimportedExtension,crossFileExtensionand the rest pass), plus the T5 triage fixturesjvm-interopandpr1948b.Validation
Tests.
__tests__/jvm-cross-language-imports.test.ts: 8 tests, all red on main, green here. The JVM resolution test files (23 files, 411 tests) pass.A/B against main
86c85cff, fresh indexes with main's kernel, edges compared by site and target. The mains since, through the base8bd55fa7, change no JVM resolution:8bd55fa7gives graphs identical to86c85cffon jextract and nowinandroid,4955eac9on those and okhttp, where this change also makes the same sites:Triage:
PhysicsAnimator.getInstance×22, okhttp's Java samples'RequestBody.create,MediaType.get,Certificates.decodeCertificatePemthrough@file:JvmName), Kotlin calls into Java classes (SystemUI'sAssert.isMainThread×15,Utils.useMediaResumption), nested Kotlin types written through their outer type (nowinandroid'sNewsFeedUiState.Success×20, mihon'sCheckboxState.State.None, okhttp'sDns.Record.IpAddress), and type references across the two languages (okhttp's Java samples referencingCall,Response,Callback).Page.State.ErrorwasRefreshResult.Error; okhttp'sHandshaker.Result.FailurewasWebSocketRecorder.Failure), onto the method rather than a field or constant of its name (TlsUtil.localhost(), mihon'sUiPreferences.timeFormat(…)companion function, jextract'sIndex_h.CXDiagnostic_Ignored()), onto the overload the argument count fits (checked by counting arguments and parameters: Minestom 799 of 843, kestra 290 of 302, spring-ai 50 of 73, playframework 33 of 37, none against; Minestom's other 44 are varargs overloads the count can't tell,Check.argCondition(cond, "msg {0}", x), read by hand, and a field or constant's name moved to its method), onto the provider class the file imports (spring-ai'sResponseFormat.builder()×13 andMetadataField.text(…)×9 had reached another provider's nested class), and spring-modulith's example modules, which each declareexample.order.Order, now link their own module's class. Two calls on untyped GraphQL results in mihon (toTrack,toTrackSearch) move from one wrong extension to another.Prompt(…)andUserMessage(…)(12 sites, 0.9) where a framework rule recorded an instantiation at 0.7; from Kotlin, a class that declares its own constructor is reached through it, as main already did for SystemUI'sInflationException(…).key.toByteArray()on aStringwasJsonObject::toByteArray,f.use {}wasArray::use,animatable.asState()wasPreference::asState). The rest are kind changes at the same place, counted once as removed and once as added: jextract's 96 calls likeIndex_h.clang_getRange()were instantiations of a same-named holder class and now call the static method, and mihon's calls likeCheckboxState.State.NoneandUpdatesUiModel.Item(…), which reached a wrong enum constant or method, now instantiate the nested class.Cost (3 interleaved fresh indexes, medians): mihon process CPU 27.7 s on main vs 27.5 s, wall 8.6 vs 8.7 s; okhttp CPU 31.5 s on both, wall 10.0 vs 9.8 s.
Suite:
CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=2 --minWorkers=1onf3f7393f(Windows 11, kernel built; 2 workers under the day's memory rule): 780 files passed, 6 skipped; 9,301 tests passed, 85 skipped, 0 failed.Follow-ups (not in this PR)
OssAuthEvent.builder()(a Lombok@Builderon a constructor, which the index doesn't synthesize) moves from one wrong target to another.class A { class B { fun f() {} } }on one line) indexesfas a top-level function and dropsAandBin both engines: a grammar quirk, found while building the fixtures.Reimplements #1948 by @mixxer.
🤖 Generated with Claude Code