xinaiz
xinaiz

Reputation: 7788

Kotlin generics fail when deduced from lambda argument

Today I encountered strange behavior while creating some Kotlin generics. Code below illustrates the problem.

MCVE

class Generic<T>(private val initial: T? = null, private val source: (T)->Unit) {
    fun test(value: T) = source(value)
}

fun <T> createGeneric(initial: T? = null, source: (T)->Unit) = Generic(initial, source)

fun test() {
    val generic = createGeneric(null) { arg: Int -> println(arg) }
    generic.test(42) 
}

The line generic.test(42) produces error:

The integral literal does not conform to the expected type Nothing

Question: How can T be deduced as Nothing, since I pass { arg: Int -> println(arg) } lambda, which is (Int)->Unit?. I double-checked type of variable generic, and it's deduced as Generic<Nothing>.

Also, if the type is deduced as Nothing, why it allows to pass lambda of type (Int)->Unit as second argument?

Kotlin Playground Example

Upvotes: 3

Views: 195

Answers (2)

Rafa
Rafa

Reputation: 3339

This is because your lambda can never execute, because the function invocation source(value) can never happen.

In Kotlin, whenever you have a function that doesn't finish, the function returns a type Nothing.

If you were to define the type that your Generic takes in Generic<Int> then you would get rid of that error. But because you don't, the compiler tries to infer it based on the value that you're passing in createGeneric(null). Because you're passing in null.

When you pass in null on a parameter type, it resolves to Nothing? and when you pass it into a nullable parameter type it resolves to Nothing. Nothing in Kotlin, is the absence of type. Because null itself doesn't have a type, you get Nothing.

Since your T is now Nothing in the context of your entire Generic class, your test function signature is now test(value: Nothing). which is why you get your error when you try to pass in an Int into that test function.

As to your second point "Why can I pass in (Int) -> Unit"?

val generic = createGeneric { arg: Int -> println(arg) }

If you were to get rid of the null parameter, you can see that the next error that you get will be because you're passing in (Int) -> Unit and it can't correctly infer that parameter type to be true.

Upvotes: 0

Alexey Romanov
Alexey Romanov

Reputation: 170713

Because functions are contravariant in their argument, (Int) -> Unit is a subtype of (Nothing) -> Unit. So both arguments are acceptable for T=Nothing and that's what gets inferred.

Yes, they are also both acceptable for T=Int, but Kotlin chooses the more specific type.

Upvotes: 2

Related Questions