Jonathan Beaudoin Jonathan Beaudoin - 2 months ago 15
Java Question

Kotlin inline Method is not Visible Unless Extending the Class

I've ran into an issue in a library I'm writing for zero-garbage collections. I've wrote a

myFunction
function but I have an issue where I can NOT call the function unless I extend the class (in this case)
RandomClass




package com.charlatano

fun main(args: Array<String>) {
val hello = RandomClass<String>()
hello.myFunction { // Unresolved reference: myFunction
}
}

class myClass {
private val list = RandomClass<String>()

fun loop() {
list.myFunction { // Unresolved reference: myFunction
}
}
}

class myClassInherit : RandomClass<String>() {
private val list = RandomClass<String>()

fun loop() {
list.myFunction { // Compiles without issue
}
}
}

open class RandomClass<out E>() {
fun iterator(): Iterator<E> {
TODO()
}

inline fun <E> RandomClass<E>.myFunction(action: (E) -> Unit): Unit {
for (e in iterator()) action(e)
}
}


Here is the error:

Error:(23, 8) Kotlin: Unresolved reference: myFunction

Answer

The issue is that you wrote an extension function for some instance of RandomClass within a different receiver of RandomClass. So it can only be used from with RandomClass where both the instance this of RandomClass can be inferred along with the explicit or implied receiver. There is no way in Kotlin to specify both an instance of a class and a different receiver at the same time. You can only do it when you specify one and the other can be implied.

The problem maybe is more obvious if we mock it up:

class A {
   inline fun B.foo() { ... }
}   

A().foo() <--- nope, need B().foo() within A()
B().foo() <--- nope, can't see B.foo() from outside A instance (scoped extension)
A::B.foo() <--- doesn't exist as syntax, I made this up

How can you specify both A and B at the same time? There is no syntax for "Instance A receiver B call foo()".

But if you are inside of A already, for example:

class A {
   inline fun B.foo() { ... }

   fun bar() { B().foo() }  <-- all good!  We have A, we have B, and can call foo
}   

The instance for A is satisfied by the class itself, and the receiver by the new instance of B being created before Foo is called. The only different from your code is that you called A instance and B receiver the same thing but they are two parameters that need to be known to make this type of function call.

In your case you have two simple options to get rid of the need for both instance and receiver:

1. Don't make myFunction an extension function, only make it inline:

open class RandomClass<out E>() {
    fun iterator(): Iterator<E> {
        TODO()
    }

    inline fun myFunction(action: (E) -> Unit): Unit {
        for (e in iterator()) action(e)
    }
}

2. move the inline extension outside of the class so it doesn't also need an instance:

open class RandomClass<out E>() {
    fun iterator(): Iterator<E> {
        TODO()
    }
}

inline fun <E> RandomClass<E>.myFunction(action: (E) -> Unit): Unit {
    for (e in iterator()) action(e)
}

Either way, you have no compiler errors anymore.

Comments