Compiler replaces generic with Nothing

This code doesn’t compile

interface SmallerThing

class Thing: SmallerThing

interface Repo<T, ID> {
    fun findById(id: ID): T
}

class ThingRepo: Repo<Thing, Long> {
    override fun findById(id: Long) = Thing()
}

class LazyEntity<T, ID>(val id: ID) {
    fun load(repo: Repo<out T, ID>): T = repo.findById(id)
}

interface Foo {
    val lazyThing: LazyEntity<out SmallerThing, Long>
}

class Bar(override val lazyThing: LazyEntity<Thing, Long>) : Foo


fun main() {
    val bar: Foo = Bar(LazyEntity(42))
    val repo: ThingRepo = ThingRepo()
    println(bar.lazyThing.load(repo))
}

with the error message:

cenasxumjVC.kt:27:32: error: type mismatch: inferred type is ThingRepo but Repo<Nothing, Long> was expected
    println(bar.lazyThing.load(repo))
                               ^

I don’t understand why this happens, ThingRepo should be a subtype of Repo<out SmallerThing, Long> and load should accept it but it complains about wanting Nothing in the first param

The compiler replaced Generic with Nothing when it could not find a type that satisfies generic boundaries. Try writing your types explicitly.

T in LazyEntity.load() is at contravariant position (in), but you declared lazyThing as LazyEntity<out SmallerThing, Long> meaning that T is covariant. As a result, it can’t be really anyhow provided. In other words, out SmallerThing means that you will only “receive” SmallerThing objects from lazy entity and never “send” any SmallerThings objects to it. Then you try the opposite: provide the repo object to its load() function with Thing inside it. Compiler forbids it and this is how it should be.

I think I understand what you try to achieve, but what if someone would do something like:

(bar as Foo).lazyThing.load(smallerThingsRepo)

Bar was supposed to only receive repos of type Thing, but we just provided a repo with SmallerThing type, so we broke type safety.

2 Likes