Jonathan Beaudoin
Jonathan Beaudoin

Reputation: 2188

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

Upvotes: 2

Views: 976

Answers (2)

Ilya
Ilya

Reputation: 23125

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

foo is called member extension function, because it is a member of class A and an extension for class B. Inside of foo there are two receivers available:

  • this@A is called dispatch receiver,
  • this@foo or simply this is called extension receiver.

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

Actually there is such syntax, you just need to have A as implicit this dispatch receiver:

with(A()) {
   B().foo()
}

Here you have instance of A specified as implicit dispatch receiver and instance of B as explicit extension receiver.

How it would look like with classes from question:

val randomClass = RandomClass<Any>()
val anotherRandomClass = RandomClass<Any>()
with(randomClass) {
    // randomClass is both dispatch receiver and extension receiver
    myFunction {  }
    // randomClass is dispatch receiver and anotherRandomClass is extension receiver
    anotherRandomClass.myFunction {  }
}

But in your case there is no need to make myFunction member extension, because it doesn't use two receivers inside. Just make it either member or extension, not both, as this answer suggests.

Upvotes: 0

Jayson Minard
Jayson Minard

Reputation: 85946

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.

Upvotes: 3

Related Questions