Current intersection-type options in Kotlin

I’ve been playing around with a use-case of Intersection types in a trait-like manner and thought I’d post some interesting and currently available things you can do using the supported Kotlin intersection types.

My use-case is a type of object with mixed properties. I could go with a catch-all container, a TypedMap, or interface hierarchy, but the traits style of interface per property seems to fit for me (although not fully traits since there is no implementation).

My use-case

For my use-case, my consumer of the top-level type can check for subtypes, cast, and capture its required properties in a new containing object (that is a new intersection) for use within the consumer. So produces would put in their own type, an intersection of various trait interfaces, and the consumer would check and convert to another intersection type.
This is perfectly fine–but I decided to explore skipping the custom container for the consumer and directly working with the intersection types. What I learned was Kotlin supports a decent bit of direct intersection past the normal Java intersection support.

The standard Java/Kotlin intersection support.

Both Java and Kotlin support intersection types in the generic context.

I can define a function that requires a type of A & B, where A and B like this:

open class A
interface B
class Both : A(), B
//sampleStart
fun <T> takeAandB(t: T) where T: A, T: B = println("Got $t")
//sampleEnd
fun main() = takeAandB(Both())

This function requires the caller knows the specific type. So in this case, we can receive a provided type as an intersection. No inspecting and creating intersection types out of thin air so far.
I didn’t bother checking but I expect this works for generic classes as well.

One limitation is that you cannot define multiple generic bounds. Meaning this won’t work:

//sampleStart
fun <T1, T2, R> intersection(): R where R: T1, R: T2 { TODO() }
//sampleEnd
fun main() = println("Compiled")

Kotlin Smart-casting

Smart-casting allows creating intersections without generics:

interface Interface1 {
    val i1: String
}
interface Interface2 {
    val i2: Int
}
class Both : Interface1, Interface2 {
    override val i1: String = "one"
    override val i2: Int = 2
}
fun anyType(): Any = Both() // We mask the type here just to add another layer of "Any" for fun.

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

    bar as Interface1
    bar as Interface2
    // Try adding another type like `bar as String`. It will fail but the inspecting will show your interstion type.
    bar // Check the type here in Intellij with `ctrl+shift+p`. Inspection bugs explained below.
    println(bar.i1)
    println(bar.i2)
//sampleEnd
}

I found inspecting the types with IntelliJ in this example behave a bit glitchy. For example, inspecting bar.i1 shows just Interface1. If you ever want to see a true type of something, try making a new line with only the property you’re inspecting on it.

Kotlin contracts

We’re not limited to as for smart-casting to create intersection types free of generics. Aside from using as? with an expression that returns Nothing, we can also use contracts to define our own intersection check:

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

@OptIn(ExperimentalContracts::class)
inline fun <reified T1, reified T2> Any.asIntersection() {
    contract {
        returns() implies (this@asIntersection is T1 && this@asIntersection is T2)
    }

    assert(this is T1 && this is T2)
}
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() {
//sampleStart
    val bar: Any = anyType()

    bar.asIntersection<Interface1, Interface2>()
    bar // Check the type here in Intellij with `ctrl+shift+p`
    println(bar.i1)
    println(bar.i2)
//sampleEnd
}

Personally, I don’t find this function more useful than multiple as casts. But I guess it could serve as an example for a function with slightly different behavior that smart-casts a property to an intersection type.

The big limitation here is that we can’t return the intersection type due to smart-casting limitations with generics. Because of this, asIntersection() may be a poor name since it’s really functioning as assertIntersection().

Here’s a version I find a bit more useful:

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

@OptIn(ExperimentalContracts::class)
inline fun <reified T1, reified T2, reified T3> Any.isIntersection3(): Boolean {
    contract {
        returns(true) implies (this@isIntersection3 is T1 &&
                this@isIntersection3 is T2 &&
                this@isIntersection3 is T3
                )
    }
    return (this is T1 &&
            this is T2 &&
            this is T3
    )
}
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() {
//sampleStart
    val bar: Any = anyType()

    if (bar.isIntersection3<Interface1, Interface2, Interface3>()) {
    // if (bar is Interface1 && bar is Interface2 && bar is Interface3) { // <-- alternative
 
        bar // Check the type here in Intellij with `ctrl+shift+p`
        println(bar.i1)
        println(bar.i2)
        println(bar.i3)
    }
//sampleEnd
}

This function allows us to avoid typing bar over and over again. Still not a ton over a few is checks but you could use this to create a function with different behavior.

Kotlin defining an intersection property (local only)

So far we’ve seen how you can smart-cast a property into an intersection type. But what if you want to create a property that is, from the start, an intersection type? We can use variable inference!

interface Interface1 {
    val i1: String
}
interface Interface2 {
    val i2: Int
}
class Both : Interface1, Interface2 {
    override val i1: String = "one"
    override val i2: Int = 2
}
fun anyType(): Any = Both() // We mask the type here just to add another layer of "Any" for fun.

fun main() {
//sampleStart
    val bar = run { // You could use other scope functions or delegation here.
        val temp = anyType()
        temp as Interface1
        temp as Interface2 // Can't return this because we haven't finished the smart-cast... bug?
        temp // This is REQUIRED! See above.
    }
    bar // Check the type here in Intellij with `ctrl+shift+p`. Inspection bugs explained below.
    println(bar.i1)
    println(bar.i2)
//sampleEnd
}

NOTE: You can’t do this with member properties or with function return types. I haven’t fully understood why yet.

This last example makes more sense for my use-case in that it enables my consumer to skip defining its own intersection class (e.x. val things: List<ConsumerIntersection> = ...) and instead can work with a collection of pure intersection: val things: List<(A & B & C & D)> = /* */, but with the type inferred.

For clarity, my use-case used:

thingsProducer.mapNotNull { if (it.isIntersectionOf4<A, B, C, D>()) it else null }

Simple usage like smart-casting to a binary intersection or usage like generics with multiple concrete bonds is pretty easy. Once local properties primarily typed as intersections get involved things start to feel a bit hacky due to the possible bugs for inspection and having to return that property alone on the last line of a lambda.

I’m not sure if I have a point to this topic other than seeing if anyone finds it interesting… I know I did.
Maybe one thought might be: IMO the discussion of intersection types would be best done as separate from union types–easing some of the intersection limitations shouldn’t be shackled by the more difficult requests for union types.

4 Likes

Hate to be the um actually guy, but this is technically possible, it’ll just look odd from the Java calling side. (The following explanation is paraphrased from this Slack thread). The stdlib actually defines a function that looks like this:

@kotlin.internal.InlineOnly
public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : CharSequence, C : R

The trick here is in the InlineOnly annotation since the checker has an exception for it.
That exception is there in the checker because the only problem with this feature is that it breaks Java compatibility as explained in this StackOverflow post, and so if a function is InlineOnly it can be called only in Kotlin and it never exists in the bytecode, thereby solving that problem, and so, if you want to enable this you can either use this trick to shadow the internal annotation, or you can suppress the error using @Suppress("BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER") (Playground example). If you’re going to suppress that error, then I’d suggest that, just in case you really care about Java compat, that you make that function inline and make one of the type parameters reified because reified inline functions don’t actually exist in the bytecode.

And yeah, using one of the aforementioned tricks you can then use that intersection function. I wonder what the possibilities could be with that function available on the type system! I don’t think it’d be anything major but it could be useful. I guess at the very least you’d be able to return an intersection type, and so in your example you can define an asIntersection3() possibly.

1 Like

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

As a bonus, here’s TypeWrapper definitions up to 22 (playground alongside the previous asIntersection function, now without the numbers):


// Inspired by Arrow's Union classes definition
sealed class TypeWrapper22<out T1, out T2, out T3, out T4, out T5, out T6, out T7, out T8, out T9, out T10, out T11, out T12, out T13, out T14, out T15, out T16, out T17, out T18, out T19, out T20, out T21, out T22> {
    @PublishedApi internal object IMPL: TypeWrapper22<Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing>()
}
typealias TypeWrapper21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21> = TypeWrapper22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, Nothing>
typealias TypeWrapper20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20> = TypeWrapper21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, Nothing>
typealias TypeWrapper19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19> = TypeWrapper20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, Nothing>
typealias TypeWrapper18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18> = TypeWrapper19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, Nothing>
typealias TypeWrapper17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17> = TypeWrapper18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, Nothing>
typealias TypeWrapper16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> = TypeWrapper17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, Nothing>
typealias TypeWrapper15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> = TypeWrapper16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, Nothing>
typealias TypeWrapper14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> = TypeWrapper15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, Nothing>
typealias TypeWrapper13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> = TypeWrapper14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, Nothing>
typealias TypeWrapper12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> = TypeWrapper13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, Nothing>
typealias TypeWrapper11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> = TypeWrapper12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, Nothing>
typealias TypeWrapper10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> = TypeWrapper11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, Nothing>
typealias TypeWrapper9<T1, T2, T3, T4, T5, T6, T7, T8, T9> = TypeWrapper10<T1, T2, T3, T4, T5, T6, T7, T8, T9, Nothing>
typealias TypeWrapper8<T1, T2, T3, T4, T5, T6, T7, T8> = TypeWrapper9<T1, T2, T3, T4, T5, T6, T7, T8, Nothing>
typealias TypeWrapper7<T1, T2, T3, T4, T5, T6, T7> = TypeWrapper8<T1, T2, T3, T4, T5, T6, T7, Nothing>
typealias TypeWrapper6<T1, T2, T3, T4, T5, T6> = TypeWrapper7<T1, T2, T3, T4, T5, T6, Nothing>
typealias TypeWrapper5<T1, T2, T3, T4, T5> = TypeWrapper6<T1, T2, T3, T4, T5, Nothing>
typealias TypeWrapper4<T1, T2, T3, T4> = TypeWrapper5<T1, T2, T3, T4, Nothing>
typealias TypeWrapper3<T1, T2, T3> = TypeWrapper4<T1, T2, T3, Nothing>
typealias TypeWrapper2<T1, T2> = TypeWrapper3<T1, T2, Nothing>
typealias TypeWrapper1<T1> = TypeWrapper2<T1, Nothing>
    
// Type "constructors". The unitx parameters are there to avoid `Overload Resolution Ambiguity` errors
// due to the fact that all of the functions are named "type"
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit, unit17: Unit = Unit, unit18: Unit = Unit, unit19: Unit = Unit, unit20: Unit = Unit, unit21: Unit = Unit): TypeWrapper22<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit, unit17: Unit = Unit, unit18: Unit = Unit, unit19: Unit = Unit, unit20: Unit = Unit): TypeWrapper21<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit, unit17: Unit = Unit, unit18: Unit = Unit, unit19: Unit = Unit): TypeWrapper20<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit, unit17: Unit = Unit, unit18: Unit = Unit): TypeWrapper19<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit, unit17: Unit = Unit): TypeWrapper18<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit, unit16: Unit = Unit): TypeWrapper17<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit, unit15: Unit = Unit): TypeWrapper16<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit, unit14: Unit = Unit): TypeWrapper15<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit, unit13: Unit = Unit): TypeWrapper14<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit, unit12: Unit = Unit): TypeWrapper13<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit, unit11: Unit = Unit): TypeWrapper12<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit, unit10: Unit = Unit): TypeWrapper11<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit, unit9: Unit = Unit): TypeWrapper10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit, unit8: Unit = Unit): TypeWrapper9<T1, T2, T3, T4, T5, T6, T7, T8, T9>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7, T8> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit, unit7: Unit = Unit): TypeWrapper8<T1, T2, T3, T4, T5, T6, T7, T8>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6, T7> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit, unit6: Unit = Unit): TypeWrapper7<T1, T2, T3, T4, T5, T6, T7>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5, T6> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit, unit5: Unit = Unit): TypeWrapper6<T1, T2, T3, T4, T5, T6>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4, T5> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit, unit4: Unit = Unit): TypeWrapper5<T1, T2, T3, T4, T5>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3, T4> type(unit1: Unit = Unit, unit2: Unit = Unit, unit3: Unit = Unit): TypeWrapper4<T1, T2, T3, T4>
    = TypeWrapper22.IMPL
inline fun <T1, T2, T3> type(unit1: Unit = Unit, unit2: Unit = Unit): TypeWrapper3<T1, T2, T3>
    = TypeWrapper22.IMPL
inline fun <T1, T2> type(unit1: Unit = Unit): TypeWrapper2<T1, T2>
    = TypeWrapper22.IMPL
inline fun <T1> type(): TypeWrapper1<T1>
    = TypeWrapper22.IMPL

(before you ask, no I did not auto-generate this (copy/paste ftw), and yes this is a cry for help lol).
To be honest I couldn’t bring myself to create asIntersection functions up to 22, I did ones for 2 and 3 in the playground link, the rest is left as an exercise for the reader of course. Side note: I’m guessing that the playground wasn’t made to handle a lot of LOC because with this it just sometimes halts for a few seconds, possibly to contemplate its existence and my existence in addition.
I added all of the unitx parameters just because I hated seeing typex everywhere. However, I now won’t be able to stop seeing the word unit engraved in my eyes for the next who-knows-how-long!

You could probably make a library out of this, possibly named InterseKT (and yes I know I know FunKTionale did it first but whatever).

EDIT: I couldn’t help but do it. I auto-generated it this time though thankfully. Here you go (apparently it reaches the character limit allowed in discourse replies which is just hilarious tbh).

3 Likes

@kyay10 this is great stuff! I’ll have to spend some time digesting it all.

I didn’t realize you could suppress a compiler error. Pretty cool–now I’ll be able to “suppress (“DOESNT_WORK”)” on all my code :laughing:

1 Like

You can suppress a LOT of things for the better or worst (Edit: just realised that it should technically be “worse”, but worst as a superlative honestly conveys my point better…). The main one that most people used to use is @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") (to the point that I memorised it by heart) which now is thankfully in Kotlin 1.4.30

2 Likes

Sorry for writing in that old thread, but I didn’t find a better place for my question:

How safe is it to use @Suppress(“BOUNDS_NOT_ALLOWED_IF_BOUNDED_BY_TYPE_PARAMETER”)? Especially for something like this: “interface Test<A, B, T> where T: A, T: B”.

The explanations I’ve encountered so far haven’t convinced me either for or against its use. As far as I understood, the problem is that there is no Java counterpart for it. But is this the only reason or is it maybe also dangerous in a pure-Kotlin-environment?
I’m also a little bit confused why the Java bytecode seems like it is supported at least on bytecode-level, javap shows “…Test<A, B, T extends A & B>…” which let me assume that it maybe is even “safe” on Java level as it just can’t be used?

1 Like

From memory (and previous research), it just might ruin the Java signature or have java callers accidentally do naughty things I think. In fact, this error suppression is not needed if you instead use the @InlineOnly annotation and mark your function inline. This however needs a tiny bit of a setup (because you need to shadow the pre-existing annotation since it’s internal). Thus, the @InlineOnly approach is likely best, especially because suppressing errors gives a warning that it might not be supported in the future.
Btw, the stdlib does use such bounds in some functions for collections IIRC, so the support for such bounds in some form likely will continue