Multiple invariant parameters leads to type compilation failure?

interface Animal
class Cat: Animal
class Dog: Animal

data class BoundedInvariant<T: Animal>(val thing: T)
data class SecondBoundedInvariant<T: Animal>(val thing: T)

fun <T: Animal>pet(invariant: BoundedInvariant<T>) {
    println(invariant.thing::class)
}
fun <T: Animal>petBoth(secondBoundedInvariant: SecondBoundedInvariant<T>, invariant: BoundedInvariant<T>) {
    println(invariant.thing::class)
    println(secondBoundedInvariant.thing::class)
}

val animalSet = mapOf(
    BoundedInvariant(Dog()) to SecondBoundedInvariant(Dog()),
    BoundedInvariant(Cat()) to SecondBoundedInvariant(Cat()),
)

fun main() {
    animalSet
        .forEach {
            pet(it.key)
            //petBoth(it.value, it.key)
        }
}

Why does pet() compile but petBoth() does not? It fails with Type mismatch: inferred type is Animal but CapturedType(out Animal) was expected

I understand that petBoth() parameters are not declared with T as covariant but why does it work in the case of pet()?
Does it fail in the case of petBoth() because the compiler cannot prove they are the same T for each iteration of forEach?
Ok let’s explore that theory a bit further:

data class Container<T: Animal>(val a: BoundedInvariant<T>, val b: SecondBoundedInvariant<T>)
val a = Container(BoundedInvariant(Dog()), SecondBoundedInvariant(Cat()))

This compiles just fine?? This is very unexpected and does not compile in Scala as expected: Scastie - An interactive playground for Scala

How can the invocation of petBoth() be fixed without changing those parameters to covariant (undesirable)?

2 Likes

In this case it.value and it.key are of type out Animal but your function expects Animal type. Just change your function parameter declaration like this:

fun <T: Animal>petBoth(secondBoundedInvariant: SecondBoundedInvariant<out T>, invariant: BoundedInvariant<out T>) {
    println(invariant.thing::class)
    println(secondBoundedInvariant.thing::class)
}

Or you can change the declaration of invariant data classes:

data class BoundedInvariant<out T: Animal>(val thing: T)
data class SecondBoundedInvariant<out T: Animal>(val thing: T)
1 Like

I think Peter is right,
With its definition of BoundedInvariant & SecondBoundedInvariant classes (without covariant ‘out’ word)

The code

Should not compile

2 Likes

I would guess that the reason it compiles is because the compiler infers the following type arguments:
Container<Animal>(BoundedInvariant<Animal>(Dog()), SecondBoundedInvariant<Animal>(Cat()))

3 Likes