Skip to content

fix(kotlin): a property carries its visibility, and a private class hides its members - #1761

Closed
danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/kotlin-property-visibility
Closed

danusha2345 wants to merge 2 commits into
colbymchenry:mainfrom
danusha2345:fix/kotlin-property-visibility

Conversation

@danusha2345

Copy link
Copy Markdown
Contributor

Follow-up to #1745 (the Kotlin / Go / Rust file-local rule, #1731). Two commits, off main at cd4e65b.

What

  1. A Kotlin property records its visibility modifier. private val / private var / internal at class or object scope were indexed with no visibility, so the Kotlin rule in isVisibleAcrossFiles — which declines a private fun in another file — read every property as public. Both the WebAssembly extractor and the native kernel now read the modifiers child the way they already do for functions; the parity fixture gains a private val and an internal var.

  2. A member of a private class or object is file-local too. private class Clock(var value: Long) { fun now() = value } in a test: the method is public, the class is not, so nothing outside the file can name now. For the languages where private is file-local (Kotlin, Java, C#, Swift, Scala, Dart, PHP) a candidate whose enclosing class/object/interface/enum in its file is private is declined — by line range among the file's nodes, memoised per node.

Applied where #1745 applies, after the whole pipeline settles, so a rejection leaves the reference unresolved.

Measured

A Kotlin + JS + Go Electron/Android app (114 Kotlin files), main at cd4e65b vs this branch, wasm arm, edge rows keyed with resolvedBy: LOST 57 / GAINED 0.

  • 24 from the property fix: a validator's token(...) onto another class's @Volatile private var token; JS now() / bytes() / gate() callers (renderer, a Python tool, the Go signaling server) onto Kotlin test fixtures' private vals; changed, handler, resources, onStatus, listener, moving.
  • 33 from the private-owner rule: 3 now() calls in the gimbal control onto the test's private class Clock, and 30 send / cancel / close / request calls from TelemetryUplink.kt / WebRtcTransport.kt (instance-method resolution) onto TelemetryUplinkTest.kt's private class FakeWebSocket / ServerSocketListener.

A second Kotlin app (FreeFCC, 1,726 nodes): 0 / 0. pulp Rust, Python and C corpora: untouched (the rule is gated to the private-is-file-local languages).

Tests

cross-file-visibility +2 (a private var and an internal val carry their modifier and the private one is declined; a public fun of a private class is declined), kernel-kotlin-parity / kernel-grammar-parity on the rebuilt kernel, resolution 198/198, frameworks-integration; tsc clean. Full suite on Linux: the viewer-less codegraph ui files, plus the three store-action tests that fail on main since #1759 and that #1760 fixes.

🤖 Generated with Claude Code

@danusha2345

Copy link
Copy Markdown
Contributor Author

Merged current main (8c9c476) into this branch: head 6ea4eb3. The only conflict was the CHANGELOG entry; no source conflict. tsc clean, the branch's tests pass.

@danusha2345

Copy link
Copy Markdown
Contributor Author

Merged current main (9181dd1, post-#1802) into this branch: head 3d50e3d. Both arms of the Kotlin property hook conflicted with #1802's initializer walk — resolved by keeping #1802's walk verbatim and adding this PR's visibility to the createNode / create_node call (TS + kernel). Kernel rebuilt; tsc clean; cross-file-visibility, kernel-kotlin-parity and extraction pass (671 tests).

@danusha2345

Copy link
Copy Markdown
Contributor Author

Merged current main (4453310) into this branch: head ddbb8c2. The only conflict was the CHANGELOG entry; no source conflict. tsc clean, the branch's tests pass.

@danusha2345

Copy link
Copy Markdown
Contributor Author

Merged main (58c07e8) into this branch; the only conflict was the CHANGELOG. Kernel rebuilt for the parity fixtures, tsc clean, full suite passes on Linux (kernel arm; a first pass had 5-second timeouts under load, all green on a rerun).

@danusha2345
danusha2345 force-pushed the fix/kotlin-property-visibility branch from 9ca360e to f9ede97 Compare September 30, 2026 18:53
@danusha2345

Copy link
Copy Markdown
Contributor Author

Rebased onto main. A bare now() into a private class is already cut on main (#2172), but a receiver call (val clock = provide(); clock.now()) still lands in CoordinatorTest::Clock::now, and private var token still captures calls from other files — the test now pins those. The visibility is carried in the new initializer walk, in both the TS extractor and the kernel.

🤖 Generated with Claude Code

@danusha2345
danusha2345 force-pushed the fix/kotlin-property-visibility branch from f9ede97 to f22f28b Compare October 1, 2026 15:42
@danusha2345

Copy link
Copy Markdown
Contributor Author

Rebased onto main (34ede4d). Since #2209 a call from production code never reaches a test file's symbol, so the private-class case as written (CoordinatorTest.kt) passed on main for that reason alone. I moved the fixture to a non-test Coordinator.kt: on main clock.now() there still lands in Coordinator::Clock::now, a member of a private nested class; with this PR it gets no edge. private var token still captures calls from other files on main. Kernel rebuilt, tsc clean; cross-file-visibility, kernel-kotlin-parity and the Kotlin resolution suites pass on both arms.

🤖 Generated with Claude Code

@danusha2345
danusha2345 force-pushed the fix/kotlin-property-visibility branch 4 times, most recently from fa5c083 to 0032b0e Compare October 10, 2026 11:35
@danusha2345
danusha2345 force-pushed the fix/kotlin-property-visibility branch 6 times, most recently from 1090ed3 to 113bebc Compare October 22, 2026 10:52
danusha2345 and others added 2 commits October 23, 2026 11:40
`private val` / `private var` at class or object scope were indexed with
no visibility, so colbymchenry#1745's Kotlin rule — which declines a `private fun`
in another file — read every property as public. On a Kotlin + JS + Go
app 24 cross-file calls landed on private properties: a validator's
`token(...)` onto another class's `@Volatile private var token`, and JS,
Go and Python callers onto Kotlin test fixtures' private fields.

Both extractors read the `modifiers` child the way functions already
do; the parity fixture gains a `private val` and an `internal var`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`private class Clock(var value: Long) { fun now() = value }` in a test:
the method is public, the class is not, so nothing outside the file
can name `now` — three `now()` calls in the app's gimbal control still
resolved onto it after the property fix. A candidate whose enclosing
class/object in its file is `private` is declined for the languages
where `private` is file-local; found by line range, memoised per node.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@danusha2345
danusha2345 force-pushed the fix/kotlin-property-visibility branch from 113bebc to 2367ce7 Compare October 23, 2026 09:08
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
…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>
@colbymchenry

Copy link
Copy Markdown
Owner

Thanks @danusha2345! This landed in #3010, with you as the commit author. Reading a Kotlin property's visibility, and keeping calls out of private classes, removed a few hundred cross-file links per Android repo that only matched by name. We kept your extractor and kernel changes as they were. We scoped the private-class rule to Kotlin, because in Scala and C# a private class can still be reached from other files of its package or partial class, and we rewrote the CHANGELOG entry.

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 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