Skip to content

Kotlin: member calls on a typed receiver produce no edge across files, and nothing across repos #3388

Description

@xiongjianxu

What happens

A Kotlin member call on a typed receiver produces no edge at all when the receiver's
class is declared in another file, and nothing survives into graph.json when it is
declared in another repository.

Two files, one corpus:

// Greeter.kt
class Greeter {
    fun greet() {}
}

// App.kt
class App(private val greeter: Greeter) {
    fun run() { greeter.greet() }
}

App.runGreeter.greet is missing. The shared cross-file pass skips member calls,
and Kotlin exports no receiver type table, so greeter is never typed: the call falls to
raw_calls and no resolver claims it. affected and every reverse-dependency query
under-report by exactly the calls that make a Kotlin call graph a call graph.

Split the same code into two repos and merge:

repo app/  App.kt      (as above)
repo lib/  Greeter.kt

merge-graphs adds nothing, because the single-repo build never recorded that App.run
wanted a Greeter.greet it could not find. This is the Kotlin instance of #3152, whose
merge pass can only finish calls a build parked.

Expected

  • greeter.greet() resolves to Greeter.greet in one corpus (INFERRED — the type came
    from a table, not from the call site), and Registry.register() on an object resolves
    EXTRACTED, matching the Swift/C#/Java tiering.
  • When the receiver's type is declared nowhere in the build, the call is parked so
    merge-graphs / global add can bind it to the single declaration another repo owns.

Notes

Every source of a receiver's type has to be read, because Kotlin's idiomatic injection
point (class App(private val greeter: Greeter)) declares no property at all — a
property-only table would miss the most common shape. The sources are: primary
constructor parameters, property declarations, function parameters, and local
val x = T() bindings whose type is only implied by the constructor call.

Ambiguity must stay unresolved rather than be guessed: two classes named Greeter, or a
receiver typed to a builtin that shares a name with a local class (class Regex vs
kotlin.text.Regex), produce no edge.

A paired PR follows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions