Why can't Kotlin infer types here?


#1
class A<K, L>
class B<K, L> {
  fun <M> map(f: (L) -> M): B<K, M> = TODO()
}
class C<N>

fun <P, Q> buildB(builder: B<P, P>.() -> B<P, Q>): B<P, Q> = TODO()

fun <R, S, T> A<R, S>.connect(b: B<in S, T>): C<T> = TODO()

val foo: C<Int> = A<Int, String>().connect(buildB { map { it.length } })
//                                 ^^^^^^^ - cannot infer T
//                                         ^^^^^^ - cannot infer Q

Is it because the type checker has to go “too deep” into the AST? On the surface of it, it could figure out that T = Q (from equating types of parameter b in connect with return type of buildB: B<in S, T> = B<P, Q>), similarly, Q = M, and finally M = Int.

A more immediate question: what would be the minimal change that would introduce minimal amount of explicit types to make it compile?


#2

“it works on my machine ¯\(ツ)/¯”
What kotlin-version are you using?


#3

Wow. I’ve tried 1.2.71 and 1.3 on the kotlinlang site, both failed.


#4

Upgraded Kotlin plugin to 1.30, and everything is fine now. Strangely, in play.kotlinlang.org everything is fine as well. I must have done something stupid, maybe a typo or something…


#5

OK, turns out it doesn’t work if we remove the hint on val foo, but instead try to use the result in the context of some known type:

class A<K, L>
class B<K, L> {
  fun <M> map(f: (L) -> M): B<K, M> = TODO()
}
class C<N> {
  fun bar(v: N): Unit = TODO()
}

fun <P, Q> buildB(builder: B<P, P>.() -> B<P, Q>): B<P, Q> = TODO()

fun <R, S, T> A<R, S>.connect(b: B<in S, T>): C<T> = TODO()

val foo = A<Int, String>().connect(buildB { map { it.length } }).bar(5)

(In fact, I was playing with this version, and then trimmed it down a bit to get a more minimal example, since it was still not compiling in 1.2.71)


#6

use this and it works.
(I assume you need a version newer than 1-3-0-rc-190, but didn’t try)


#7

Thanks, it works indeed! I’ve tried 1.3.0 release version as available on the Github.