Allow strengthening of covariant/weakening of contravariant types in interface implementations/subclasses

The following does not typecheck:

interface A
interface B : A

interface SomeInterface {
    fun covariant(): A
    fun contravariant(arg: B)
}

class SomeImplementation : SomeInterface {
    override fun covariant(): B { (...) } // strengthen
    override fun contravariant(arg: A) { (...) } // weaken
}

even though the implementation is completely safe.

I encountered a need for this in the specific case of writing EDSLs:

interface EdslSpec {
    fun primitive(closure: A.() -> Unit) // [A] covariant
}

class EdslImpl : EdslSpec {
    fun primitive(closure: B.() -> Unit) // strengthen
}

This would allow the dsl user to rely on B-specific things as long as we know EdslImpl is used. But I imagine there are other use-cases as well (e.g. the implementation class trying to use it’s own interface implementations)

The first example actually works: Java does allow covariant method return types, and Kotlin follows it.

For what it’s worth I think the reason the contravariant weakening is not allowed is because of its interaction with overloading. Is it an override, or an overload?

1 Like

I’ve found a weird way to make contravariance:

interface A
interface B : A

interface SomeInterface {
  val contravariant: (B) -> Unit
}

class SomeImplementation : SomeInterface {
  override val contravariant: (A) -> Unit = { a -> } 
}

It looks exactly like function for user code.

2 Likes