Cast reified generic type as non-null with Any upper bound

965 Views Asked by At

I’m trying to use a reified type parameter to check if the type argument is nullable, returning a different class implementation based on the nullability of the type argument. This works well, except for the non-null subclass requiring its generic type to have a non-null Any upper bound, in order to have a KClass<T> constructor argument.

This code works as expected:

interface Test
class NullableT<T> : Test
class NonNullT<T> : Test

inline fun <reified T> test(): Test {
    return if (null is T) {
        NullableT<T>()
    } else {
        NonNullT<T>()
    }
}

test<String?>()::class.simpleName // NullableT
test<String>()::class.simpleName  // NonNullT

However, this code has a compiler error:

interface Test
class NullableT<T> : Test
class NonNullT<T : Any>(tClass: KClass<T>) : Test

inline fun <reified T> test(): Test {
    return if (null is T) {
        NullableT<T>()
    } else {
        NonNullT<T>(T::class) // <-- error with <T>
        // Type argument is not within its bounds. Expected: Any Found: T
    }
}

Following the check for !(null is T), there needs to be some way to cast T as having a non-null Any upper bound.

It’s possible to make a non-null T optional. This works:

interface Test
class NullableT<T> : Test
class NonNullT<T : Any> : Test

inline fun <reified T : Any> test(nullable: Boolean): Test {
    return if (nullable) {
        NullableT<T?>()
    } else {
        NonNullT<T>()
    }
}

test<String>(true)::class.simpleName // NullableT
test<String>(false)::class.simpleName // NonNullT

But I need a way to make a nullable T non-null. This isn’t valid:

interface Test
class NullableT<T> : Test
class NonNullT<T : Any> : Test

inline fun <reified T> test(nullable: Boolean): Test {
    return if (nullable) {
        NullableT<T>()
    } else {
        NonNullT<T!!>() // Type parameter 'T' is not an expression
    }
}
2

There are 2 best solutions below

6
On

This works:

import kotlin.reflect.*
interface Test
class NullableT<T> : Test
class NonNullT<T : Any>(tClass: KClass<T>) : Test

inline fun <reified T> test(dummy: Nothing? = null): Test {
    return NullableT<T>()
}
inline fun <reified T: Any> test(): Test {
    return NonNullT<T>(T::class)
}


fun main(){
    println(test<Any>().toString())
    println(test<Any?>().toString())
    println(test<String>().toString())
    println(test<String?>().toString())
}

"But why does this work?" I hear you asking. Well simply, what's happening here is that the Kotlin compiler prefers functions that match the number of passed in parameters (in this case 0) over functions that have default parameters that would result in allowing the number of passed in parameters (in other words, if you call a function with 1 argument, the compiler will prefer a function with 1 parameter over a function with 2 parameters where the 2nd parameter has a default value). However, because T has a different upper bound in the 2 functions, if the type that you're trying to invoke test with doesn't follow the upper bound of the preferred function (which in this case is the one that returns NonNullT), the compiler will fall back to calling the more broad test function (i.e. the one that returns NullableT). This solution is kinda hacky, but sadly I don’t think there’s any other way to implement it.

6
On

No, you can't add dynamic type bound to type parameter since it can be checked only at compile-time because of type erasure.

Kotlin Nullability feature doesn't exist at compile-time too.