Skip to content

fix(kotlin): a call on an outside type reaches the project's extension on it - #2258

Closed
danusha2345 wants to merge 1 commit into
colbymchenry:mainfrom
danusha2345:fix/kotlin-project-extension-on-imported-type
Closed

danusha2345 wants to merge 1 commit into
colbymchenry:mainfrom
danusha2345:fix/kotlin-project-extension-on-imported-type

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Summary

#2196 made a Kotlin import from outside the project own its name: after import androidx.compose.ui.Modifier, Modifier.fillMaxSize() no longer lands on a same-named project method. That rule returns before every other strategy, so a project extension declared on the imported type can't be reached either:

// ui/Ext.kt                              // ui/Screen.kt
package app.ui                            package app.ui
import androidx.compose.ui.Modifier       import androidx.compose.ui.Modifier
fun Modifier.pad(): Modifier = this       fun screen() { Modifier.pad() }   // no edge

(Before #2196 this call was already dropped by the "receiver names a type the project doesn't declare" guard in matchMethodCall; #2196 adds a second block in front of every strategy, so it can no longer be fixed there.)

Change

Inside the outside-import rule, a Kotlin Type.member call now resolves to a project extension indexed as Type::member when

  • the call site can see it — same file or package, or imported by name or .* (the existing isKotlinTopLevelVisible / kotlinFileScope), and
  • the extension's own file imports the same outside type (so an extension on javax.lang.model.element.Modifier is not Compose's).

If several qualify, only one in the calling file is taken; otherwise no edge. Everything else stays unlinked as before — Modifier.fillMaxSize() included. Color.fromHex() on a fun Color.Companion.fromHex() is covered the same way. Chains through an outside call (Modifier.fillMaxSize().pad()) are untouched: the outside call's return type is unknown, and they did not resolve before #2196 either. An instance receiver (val m: Modifier; m.pad()) already resolved on main and is unaffected.

Tests

__tests__/kotlin-extension-on-imported-type.test.ts (fails on main, passes here, kernel and wasm extraction): same-package call, imported-by-name and .* calls, an unimported extension (no edge), an extension on a different Modifier type (no edge), and Compose's own member (no edge). Dropping either check yields a wrong edge in the test. Full suite on Linux passes; the Kotlin suites and #2196's java-outside-imports.test.ts pass with CODEGRAPH_KERNEL_EXPECT=1.

Measurements

Three Android apps (132–793 JVM files): edges identical before and after (0 added / 0 removed) — they call their own extensions through instances only, which #2196 never blocked. So this is a precision-safe fix for the Type.extension() shape rather than a measured win on these repos.

The visibility check follows the kotlinExtensionInScope idea from @mixxer's #1948 discussion; thanks!

🤖 Generated with Claude Code

danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 1, 2026
…bymchenry#2259, colbymchenry#2260) into fork main

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/kotlin-project-extension-on-imported-type branch 4 times, most recently from 35d410d to 48d1d19 Compare October 7, 2026 16:08
@danusha2345
danusha2345 force-pushed the fix/kotlin-project-extension-on-imported-type branch 2 times, most recently from f259946 to e1e08de Compare October 15, 2026 17:39
@danusha2345
danusha2345 force-pushed the fix/kotlin-project-extension-on-imported-type branch 4 times, most recently from e656706 to df52d43 Compare October 21, 2026 17:53
…n on it

colbymchenry#2196 made a Kotlin import from outside the project own its name, so
`Modifier.fillMaxSize()` after `import androidx.compose.ui.Modifier` no
longer lands on a same-named project method. The rule returns before any
other strategy, so a project extension on that type - Compose's
`fun Modifier.pad()`, indexed as `Modifier::pad` - could not be reached
by `Modifier.pad()` either (nor `Color.fromHex()` on a
`fun Color.Companion.fromHex()`).

Inside the rule, a Kotlin `Type.member` call now resolves to a project
extension `Type::member` that the call site can see (same file or
package, or imported by name or `.*`, as for other top-level Kotlin
declarations) and whose own file imports the same type. Anything else
stays unlinked, as before. The visibility check follows the idea of
`kotlinExtensionInScope` from the discussion in colbymchenry#1948.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/kotlin-project-extension-on-imported-type branch from df52d43 to 77814af Compare October 22, 2026 10:44
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 24, 2026
…mchenry#2876; 11 landed PRs yield to main; EXTRACTION_VERSION 55)

Main landed eleven of our PRs as our own commits with the maintainer's additions
(colbymchenry#2847, colbymchenry#2848, colbymchenry#2850, colbymchenry#2851, colbymchenry#2853, colbymchenry#2854, colbymchenry#2855, colbymchenry#2856, colbymchenry#2868, colbymchenry#2874, colbymchenry#2875), the
traversal half of colbymchenry#2839 (colbymchenry#2860) and the trail hardening of colbymchenry#1667 (colbymchenry#2869). Where the
integration carried our original version, main's wins: play.ts, routes.ts, the
tsjs kernel extractor, import-resolver's PHP alias lookup, isDecisiveSameFileMatch,
matchNewReceiverCall and the tests of those PRs. Kept beside main: colbymchenry#1952's
enclosing-scope member pick, colbymchenry#1936's closest-copy confidence, colbymchenry#2258's Kotlin
imported-type block, colbymchenry#1875's commitHdlProfile on indexFiles and hdlAccess, colbymchenry#1583's
impact budget (the type-hierarchy walk itself is main's), colbymchenry#1736's C #if tests.
22 changelog bullets of closed PRs removed where main has its own wording.

EXTRACTION_VERSION 55: Rust receiver chains (colbymchenry#2846), `new C().m` shapes (colbymchenry#2854),
Java records (colbymchenry#2868), Go method exportedness (colbymchenry#2857), generic supertypes (colbymchenry#2856).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
danusha2345 pushed a commit to danusha2345/codegraph that referenced this pull request Oct 28, 2026
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56)

Main landed ten more of our PRs as our own commits with the maintainer's additions
(colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736,
colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the
module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's
wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers,
the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if
collapse, the module-path resolution of Rust paths, the trail/path hardening in
utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's
implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of
colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets.

EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
colbymchenry pushed a commit that referenced this pull request Oct 29, 2026
…n on it

A Kotlin import from outside the project owns its name (#2196), so
Modifier.fillMaxSize() after import androidx.compose.ui.Modifier never
lands on a same-named project method. That rule returns before any other
strategy, so a project extension on the imported type, Compose's
fun Modifier.pad() (indexed as Modifier::pad), could not be reached by
Modifier.pad() either, nor Color.fromHex() on a
fun Color.Companion.fromHex().

Inside the rule, a Kotlin Type.member call now resolves to a project
extension Type::member that the calling file can see (same file or
package, or imported by name or with .*, as for other top-level Kotlin
declarations) and whose own file imports the same type. Anything else
stays unlinked, as before.

Validation:
- kotlin-extension-on-imported-type.test.ts: 2 cases, red on main,
  green here.
- A/B against main 13f983d (with the Kotlin receiver typing of #1933
  and #1761's private rule): mihon +26, all Compose Modifier
  extensions the project declares (secondaryItemAlpha 15,
  clickableNoIndication 5, selectedBackground 2, two scrollbars,
  keepInSection), nowinandroid +2 (Modifier.notificationDot,
  DeviceConfigurationOverride.WindowInsets), all right; okhttp,
  SystemUI, jextract, click and gin identical. The same sites as
  against main 4eb8c2a, before #1933.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a): CPU
  25.7 s on main vs 25.6 s, wall 8.2 s vs 8.4 s.

Original PR #2258 by @danusha2345.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @danusha2345! This landed in #3011, with you as the commit author. Calls like Modifier.secondaryItemAlpha() now reach the project's own Compose extensions: 28 new links across mihon and nowinandroid in our A/B, with nothing lost, and it works alongside the Kotlin receiver typing from #1933. We only moved the CHANGELOG entry next to the other Kotlin ones.

colbymchenry pushed a commit that referenced this pull request Oct 29, 2026
…when two share a name

When several classes share a name, resolveMethodOnType picks the one the
call file imports by matching the import's path against the candidate's
file path (com/example/b/FooConverter.kt). That holds for Java, where a
public class names its file, but a Kotlin class often lives in a file
named otherwise (FooConverter in Converters.kt), so no candidate matched
and the call went to whichever class came first.

The imported name is now matched against each candidate's qualified
name first: import com.example.b.FooConverter expects the method
com.example.b::FooConverter::convert, which the package directive gives
every JVM declaration whatever its file is called. The file-path match
stays as the fallback.

The Java calls this once fixed, a Java file calling a Kotlin class it
imports (okhttp's Java tests and mockwebserver3.MockWebServer,
SystemUI's BlurUtils), are now resolved on main by the receiver's
declared type (#1942), so on today's main the change shows in Kotlin.

From #539 we take only this change and its test. Its other two parts,
indexing Kotlin class properties and reading Kotlin imports without a
trailing semicolon, have since landed on main another way.

Validation:
- resolution.test.ts: the Kotlin same-name case (#314's shape in
  Converters.kt files) is red on main, green here.
- A/B against main 40f5851 (with the Java receiver typing of #1942
  and the Kotlin changes of #1933, #1761 and #2258): no edge changes
  on okhttp, SystemUI, nowinandroid, mihon, jextract, spring-modulith,
  Minestom, spring-ai, kestra-java, playframework, click or gin; none
  of them has the test's shape on a resolved call. Against main
  4eb8c2a, before #1942, it moved the 8 Java calls above onto the
  Kotlin class their file imports; main now reaches them at 0.9.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a): CPU
  25.7 s on main vs 25.2 s, wall 8.2 s vs 8.3 s.

Original PR #539 by @arttttt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants