fix(kotlin): a bare call reaches only a member in reach - #2172
Merged
Merged
Conversation
A receiver-less Kotlin call exact-matched any class's same-named method:
koin's module { } in one test went to another test class's private
module 268 times, error("…") to a Logger's error, get() inside a
definition lambda to Koin's get rather than the Scope it runs on.
A bare call now reaches a method of a class, anonymous object or extension
receiver around it (read from nodes and from the source's braces, so a
class the parser lost still counts) and what those inherit (with Android
framework ancestry), of a type the project's function types take as a
lambda receiver (plus outside types it writes extensions on), or of an
object the file imports. with/apply/run blocks and .kts scripts are left
unjudged; Kotlin's require/check/assert preconditions are the stdlib's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
colbymchenry
added a commit
that referenced
this pull request
Sep 30, 2026
#2172 made the members of every type a project function type takes as a receiver reachable from any bare call, since it could not tell which lambda a call is in. With several such types in reach, proximity chose: koin's `single { C51(get(), get()) }` and `Scope.new(...)`'s `get()` went to `Koin.get` (koin also has `Koin.() -> ...` lambdas), 2,190 times. - A lambda's receiver is read from the function it is passed to: the type before `.(` in its last parameter's function type, directly or through a typealias (`definition: Definition<T>`, `typealias Definition<T> = Scope.(ParametersHolder) -> T`), when every project function of that name agrees. The brace frames put it in the lambda's hierarchy. - Of the members a bare call can reach, those its lexical hierarchy reaches - its class, an extension's receiver, the lambda it is in - come before those only the project-wide receiver set reaches. koin: `get()` -> Scope (2,184), `viewModel` in `scope { }` -> ScopeDSL, `modules(...)` in `startKoin { }` -> KoinApplication. Exposed: `varchar(...)`, `integer`, `reference`, `datetime` in a table object -> Table's (were a test table's same-named property). kotlinx.coroutines: `launch { }` -> CoroutineScope.launch (was a test DSL's). Edge counts ~unchanged (targets move): koin, Exposed, okhttp, coroutines, okio; nowinandroid, moshi unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
A receiver-less Kotlin call exact-matched any project class's same-named method:
module { … }in one test went to another test class's privatemodule268 times;error("…")went to a Logger'serror;get()inside a definition lambda went toKoin::getrather than theScopethe lambda runs on.A bare call now reaches a method only when its class is in reach:
object : Table() { … }, or an extension function's receiver. These are read from the nodes and from the source's braces, so a class the parser lost still counts. What they inherit also counts, including the Android framework ancestry, so an extension onComponentCallbacksis in reach of anAppCompatActivity.Module.() -> Unit,Scope.(ParametersHolder) -> T,JdbcTransaction.(TestDB) -> Unit) and what it inherits. Also any outside type the project writes extensions on (fun MacrobenchmarkScope.waitForContent(),fun StringBuilder.padInt(…)).import okio.TestUtil.deepCopy, or a companion member import.with(x) {/x.apply {/x.run {blocks and in.ktsscripts.require(n >= 0) { … }/check(!closed)belong to the standard library, not a memberrequire(byteCount: Long).A/B (wasm, main → fix), edges −/+:
The added edges are the right targets:
module {→org.koin.dsl::module(267);addLogger(…)→Transaction::addLogger;fileSystem.read(path) { readUtf8() }→BufferedSource::readUtf8;write { writeUtf8() }→BufferedSink::writeUtf8.The removed edges are stdlib/Gradle/Compose calls, lambda-parameter invocations, and wrong-class members.
kotlin-top-level-visibility's fixture now calls itsassertEqualsextensions inside awithTables { }receiver lambda, as real Exposed code does. Before, the fixture called them where no receiver was in scope, which isn't valid Kotlin.The full suite passes except
ui-server-api"engine's own busiest symbol", which reads this checkout's own local index and fails on main too.🤖 Generated with Claude Code