Why the method I need was not called?

Could you please explain why the method I need was not chosen in the following cases?

Versions used

Java: 1.8.0_251 Oracle (also checked on 11.0.5 AdoptOpenJDK HotSpot)
IDE: IntelliJ IDEA 2020.1.2 (Community Edition) Build #IC-201.7846.76, built on June 1, 2020
Kotlin plugin: 1.3.72-release-IJ2020.1-5

  1. The case where everything works fine
import java.math.BigDecimal

fun main() {
    printAny(5.0.toBigDecimal())
    printAny(listOf(5,6,7))
    printAny(1.2)
}

fun printAny(obj: Any) {
    println(obj.toPrintable())
}

fun BigDecimal.toPrintable(): String {
    return toPlainString()
}

// case 1 - normal
fun Iterable<*>.toPrintable(): String {
    return joinToString(",")
}

fun Any.toPrintable(): String {
    return when(this) {
        is BigDecimal -> toPrintable()
        is Iterable<*> -> toPrintable()
        else -> "(default)" + toString()
    }
}
Result

5.0
5,6,7
(default)1.2

  1. Use generic type in Iterable.toPrintable() (please, don’t ask why :slight_smile:)
fun <T> Iterable<T>.toPrintable(): String {
    return joinToString(",")
}

As a result, we will get StackOverflowError because the method will not be used and we will call Any.toPrintable() again and again.

  1. Use generic again but for Iterable we will use a wildcard.
fun <T> Iterable<*>.toPrintable(): String {
    return joinToString(",")
}

And again we will get StackOverflowError.

P.S. In all cases, the IDE displayed correctly whether the method is used or isn’t. However, when I used the previous version of the Kotlin plugin (unfortunately, I didn’t check the version before updating) the IDE showed that methods are used in the 2nd and the 3rd cases.

P.P.S If we check generated classes we will see that in the first case a cast to Iterable will be used
$this$toPrintable instanceof Iterable ? toPrintable((Iterable)$this$toPrintable)
When in other cases it won’t
$this$toPrintable instanceof Iterable ? toPrintable($this$toPrintable)

P.P.P.S. The test project is attached.
test-kt-bug.zip (58.4 KB)

Just thought I’d add a runnable example. I recommend the runnable examples because you can edit it to try modifications right from the forum. I left in the whole example so it’s editable not just a sample.

Try adding <T> (which is case 3) to the function below.

import java.math.BigDecimal

fun main() {
    printAny(5.0.toBigDecimal())
    printAny(listOf(5,6,7))
    printAny(1.2)
}

fun printAny(obj: Any) {
    println(obj.toPrintable())
}

fun BigDecimal.toPrintable(): String {
    return toPlainString()
}

// case 1 - As shown
// case 2 - Change to "fun <T> Iterable<T>.toPrintable(): String"
// case 3 - Change to "fun <T> Iterable<*>.toPrintable(): String"
fun Iterable<*>.toPrintable(): String {
    return joinToString(",")
}

fun Any.toPrintable(): String {
    return when(this) {
        is BigDecimal -> toPrintable()
        is Iterable<*> -> toPrintable()
        else -> "(default)" + toString()
    }
}
1 Like

Sounds like the intended behavior specified in the Spec

Any non-parameterized callable (which does not have type parameters in its declaration) is a more specific candidate than any parameterized callable

Thanks for the reference to the spec.

If I read it correctly the presence of any type parameter in a function declaration (even if it is not used) makes the function less specific than a function that doesn’t have any type parameter in the declaration at all.

Please, correct me if I didn’t get it right.

I did a small example to check it. And I guess the reason why the function wasn’t chosen is that there wasn’t enough information to infer the type. So the compiler chose another function.

fun main() {
    printAny("")
    printAny(listOf<Any>())
    printAny(2)
}

fun printAny(obj: Any) {
    when (obj) {
        is String -> obj.doSomething()
        is Iterable<*> -> obj.doSomething()
        else -> obj.doSomething()
    }
}

// If you add the type parameter here
// you will get a compilation error on line
//   is String -> obj.doSomething()
fun /*<T>*/ String.doSomething() {
    println("String")
}

fun Iterable<*>.doSomething() {
    println("Iterable")
}

fun Any.doSomething() {
    when (this) {
        // but you won't get an error here
        // the method for Any will be chosen instead
        is String -> doSomething()
        is Iterable<*> -> doSomething()
        else -> println("default")
    }
}