Skip to content

Kotlin: member calls on typed receivers (property / getter return / local val) never resolve to method-level calls edges — Kotlin port of the Swift #1604 fix #1699

Description

@jerryliurui

Environment

  • graphify 0.9.7 (PyPI graphifyy), macOS, Python 3.10
  • Extraction invoked directly via graphify.extract.extract(files, parallel=False)

Summary

In Kotlin, member calls on a typed receiver never resolve to a method-level calls edge, across all three common receiver shapes:

  1. class property receiver (input.updateKeyboardShow(false)) → no edge at all
  2. getter-returned receiver (getInputView().updateKeyboardShow(true)) → no edge at all (the call to the getter itself IS captured)
  3. constructor-assigned local val (val view = InputView(); view.updateKeyboardShow(true)) → class-level INFERRED edge only, never method-level

This is exactly the pre-0.9.5 Swift behavior that #1604 fixed via _swift_local_var_types: Kotlin today matches Swift's old "Case C partial / Case A nothing" matrix. Requesting the same treatment for the Kotlin extractor (local-var type table + property/return-type tables). Related: #1696 (Java receiver info dropped), #1682 (PHP member calls).

Reproducer (3 files)

InputView.kt

package demo

class InputView {
    fun updateKeyboardShow(show: Boolean) {
        val visible = show
    }
}

ChatFragment.kt

package demo

class ChatFragment {
    private val input = InputView()

    fun getInputView(): InputView {
        return input
    }

    fun onPanelOpen() {
        getInputView().updateKeyboardShow(true)   // shape 2: getter-returned receiver
    }

    fun onPanelClose() {
        input.updateKeyboardShow(false)           // shape 1: property receiver
    }

    fun onPanelToggle() {
        val v = getInputView()
        v.updateKeyboardShow(true)                // shape 2b: local val from getter
    }
}

LocalVarCtor.kt

package demo

class LocalVarCtorPage {
    fun open() {
        val view = InputView()
        view.updateKeyboardShow(true)             // shape 3: constructor local val
    }
}

Current behavior (0.9.7)

inputview_inputview_updatekeyboardshow exists as a node, but the only incoming edge it ever gets is its own class method edge. Full relevant edge list:

chatfragment_chatfragment_onpanelopen  --calls--> chatfragment_chatfragment_getinputview  [EXTRACTED]
chatfragment_chatfragment_onpaneltoggle --calls--> chatfragment_chatfragment_getinputview [EXTRACTED]
chatfragment_chatfragment_getinputview --references--> inputview_inputview               [EXTRACTED]
localvarctor_localvarctorpage_open     --calls--> inputview_inputview                    [INFERRED]   <- class-level only

Note the extractor already knows getInputView() returns InputView (the references edge above) and that input is initialized with InputView() — the type information exists but is not used to resolve the member calls.

Expected

onPanelOpen  --calls--> InputView.updateKeyboardShow  [INFERRED]
onPanelClose --calls--> InputView.updateKeyboardShow  [INFERRED]
onPanelToggle --calls--> InputView.updateKeyboardShow [INFERRED]
open         --calls--> InputView.updateKeyboardShow  [INFERRED]  (method-level, not class-level)

Real-world impact

Found while indexing a large production Android monorepo (~5,900 Kotlin/Java files): a one-line bugfix commit changed a call reached via getInputView().updateKeyboardShow(...); asking the graph "who calls updateKeyboardShow" returned zero callers, while grep and a Hybrid-LSP-based tool both found the caller immediately. Since the Swift side of this exact problem was fixed in 0.9.5 and verified working on our iOS codebase, Kotlin is now the platform with the largest remaining caller-resolution gap for us.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions