Current intersection-type options in Kotlin

I guess I have too much time on my hands, but here’s an implementation of asIntersection3, albeit with a tiny caveat (playground):


import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

@Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER")
@OptIn(ExperimentalContracts::class)
// R is automatically inferred to just be T1 & T2 & T3. I used the TypeWrapper trick so that then
// the callsite doesn't have to supply a type for R since you can't explicitly write out intersection types.
inline fun <reified T1, reified T2, reified T3, R> Any.asIntersection3(type: TypeWrapper3<T1, T2, T3>): R?
        where R : T1, R : T2, R : T3
{
    // You can add this if you really want to so that the receiver is from here on known to be
    // of the intersection type T1, T2, and T3 so that one can just do `if(x.asIntersection3(blablabla) != null)` 
    // and use x itself after that.
    contract {
        returnsNotNull() implies (this@asIntersection3 is T1 &&
                this@asIntersection3 is T2 &&
                this@asIntersection3 is T3
                )
    }
    // I tried to use takeIf here and it didn't work by the way; bummer!
    return if(this is T1 &&
            this is T2 &&
            this is T3) this as R else null
}
interface Interface1 {
    val i1: String
}
interface Interface2 {
    val i2: Int
}
interface Interface3 {
    val i3: Boolean
}
class Both : Interface1, Interface2, Interface3 {
    override val i1: String = "one"
    override val i2: Int = 2
    override val i3: Boolean = true
}
fun anyType(): Any = Both() // We mask the type here just to add another layer of "Any" for fun.

fun main() {
    val bar: Any = anyType()

    bar.asIntersection3(type<Interface1, Interface2, Interface3>())?.let { baz ->
        baz // Check the type here in Intellij with `ctrl+shift+p`
        println(baz.i1)
        println(baz.i2)
        println(baz.i3)
    }
}

sealed class TypeWrapper3<out T1, out T2, out T3> {
    @PublishedApi internal object IMPL: TypeWrapper3<Nothing, Nothing, Nothing>()
}

inline fun <T1, T2, T3> type(): TypeWrapper3<T1, T2, T3> = TypeWrapper3.IMPL

As explained in the code, the callsite can’t really provide R, and so I had to go with a trick to carry that type information around. Idk how or why but I came up with that TypeWrapper trick when I was experimenting a bit with making DSLs nicer to use, and it just stuck with me ever since.

3 Likes