Skip to content

fix(kotlin): a private property, and the members of a private class, stay in their file - #3010

Merged
colbymchenry merged 1 commit into
mainfrom
claude/pr-1761-kotlin-private-visibility
Oct 29, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/pr-1761-kotlin-private-visibility

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

Summary

In Kotlin, a private val or private var property, and the members of a private class or object, no longer take calls from other files. Nothing outside their file can name them, but cross-file calls of the same name landed on them: collections' filter { … } on a class's private IntentFilter filter, OkHttp enqueue(…) calls on a private inner class's enqueue, JavaScript, Go or Python callers on a Kotlin test fixture's private val.

Root cause

A Kotlin property was indexed without its visibility modifier, so the Kotlin file-local rule (#1745), which declines a private fun from another file, read every property as public. A member of a private class or object carries no private of its own, so the rule let it through as well.

From the contributor, and what we changed

From @danusha2345 (#1761), unchanged: both extractors (the wasm path and the native kernel) read a property's modifiers as they already did a function's; the parity fixture gains a private val and an internal var; isVisibleAcrossFiles declines a candidate inside a private type in its file (by line range, memoised per node); the two cross-file-visibility.test.ts cases.

Our changes:

  • The private-owner rule is Kotlin's alone. The PR applied it to every language where private is file-local. A Scala private[pkg] class, or one with a private constructor, belongs to its package, and a C# private nested class serves every file of its partial class: on main of that day, http4s lost 742 edges with the rule (right ones among them, like stream.receiveHeaders() on the private[h2] class H2Stream), scalachess 91, serilog 10 (9 of them right typed edges). A Kotlin private constructor leaves its class public, which the rule never looked at.
  • CHANGELOG: rewritten in the house format, after the other Kotlin call bullets.
  • Rebased onto main 01e0ac3a, which has fix(kotlin): type receivers from declarations; parse fun interface #1933's Kotlin receiver typing (fix(kotlin): a call through a property or a receiver chain resolves on its declared type #2972); the patch applies unchanged.

Validation

Tests. cross-file-visibility.test.ts: the 2 Kotlin cases are red on main and green here; kernel-kotlin-parity.test.ts green with the rebuilt kernel.

Kernel parity (scripts/kernel-parity.mjs --lang kotlin), 0 files with diffs: okhttp 588/619 files byte-identical, mihon 891/940, nowinandroid 340/350, SystemUI's Kotlin files 188/198, the torture fixture 1/1; the rest are deferred to wasm on parse errors, the same files as with main's kernel.

A/B against main 01e0ac3a, fresh indexes with each arm's kernel, edges compared by site and target identity:

repo edge sites added removed retargeted
square/okhttp c317930 0 274 0
mihonapp/mihon d001e6f 0 177 0
LineageOS SystemUI (lineage-18.1, sparse; Java + Kotlin) 0 94 0
android/nowinandroid 2982e63 identical
openjdk/jextract (Java), pallets/click, gin-gonic/gin (controls) identical

Every removed edge, checked one by one against the new graph, is a call from another file onto a private Kotlin property (okhttp 54, mihon 154, SystemUI 5) or onto a member of a private Kotlin class or object (220, 23, 89). The largest groups: okhttp's private inner AndroidDns.AndroidQuery.enqueue (71) and RealWebSocketTest's private TestStreams.source (59); mihon's private val filter = IntentFilter() that collections' filter { … } reached (65) and MangaRestorer's private now (47); SystemUI's private PrivacyItemController.MyExecutor.execute (43) and ControlsBindingControllerImpl.CallbackRunnable.run (13).

Before #1933, against main 4eb8c2a5, the same change removed okhttp 307, mihon 237 and nowinandroid 7 (MutableStateFlow.update { … } reaching a private ScrollThumbElement.update). The difference is calls #1933 now handles itself: it leaves a call on a library type unlinked, or types it onto its declared class (mihon's register calls reach the Preference.register extension, okhttp's connectionCount() reaches ConnectionPool), so they no longer land on a private declaration. http4s, scalachess (Scala) and serilog (C#) were identical in that run, as the private-owner rule is Kotlin's alone.

Cost (mihon, median of 3 interleaved fresh indexes, on main 4eb8c2a5): process CPU 25.7 s on main vs 25.5 s, wall 8.2 s on both.

Suite: CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1 on 2aecd2ed (Windows 11, kernel built): 774 files passed, 6 skipped; 9,245 tests passed, 85 skipped, 0 failed.

Lands #1761 by @danusha2345.

🤖 Generated with Claude Code

…stay in their file

A Kotlin property was indexed without its visibility modifier, so the
Kotlin file-local rule (#1745), which declines a private fun in another
file, read every property as public: a call like token(...) could link
to another file's @volatile private var token, and JavaScript, Go or
Python callers to a Kotlin test fixture's private val. A public member
of a private class or object was reachable from other files too, though
nothing outside its file can name it.

Both extractors (wasm and the native kernel) now read a property's
modifiers the way they read a function's, and a candidate inside a
private Kotlin class or object is declined from another file. That
second rule is Kotlin's alone: a Scala private[pkg] class, or one with
a private constructor, is its package's, and a C# private nested class
serves every file of its partial class. Applied to every language where
private is file-local, as the PR had it, it removed 742 edges on http4s
(right ones among them, like stream.receiveHeaders() on a private[h2]
class H2Stream), 91 on scalachess and 10 on serilog (9 of them right
typed edges).

Validation:
- cross-file-visibility.test.ts: 2 cases, red on main, green here; the
  kernel parity fixture gains a private val and an internal var.
- Kernel parity (kotlin), 0 diffs: okhttp 588/619 files, mihon 891/940,
  nowinandroid 340/350, SystemUI's Kotlin 188/198 byte-identical, the
  same files deferred to wasm as with main's kernel; torture 1/1.
- A/B against main 01e0ac3 (with the Kotlin receiver typing of #1933):
  okhttp -274, mihon -177, SystemUI -94, every one a call from another
  file onto a private Kotlin property or a member of a private class or
  object (okhttp's private inner AndroidQuery.enqueue and
  RealWebSocketTest's private TestStreams, mihon's private IntentFilter
  filter that collections' filter { } reached, SystemUI's private
  MyExecutor.execute); nothing added. nowinandroid, jextract, click and
  gin identical; against main 4eb8c2a, http4s, scalachess and serilog
  were identical too.
- Cost (mihon, median of 3 interleaved indexes, on main 4eb8c2a): CPU
  25.7 s on main vs 25.5 s, wall 8.2 s on both.

Original PR #1761 by @danusha2345.

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.

1 participant