Converting nullable type parameter to a non-nullable type


#1

I am attempting to write a function that will wrap an optional object with a dynamic proxy. I want the function to inherit the nullability of the type returned by the function that is passed to it: If the passed-in function returns null, then the proxy-creating function will also return null. For the creation of the proxy, I also need the class of the interface of the returned type.

I have tried a number of variants, but none of them are satisfactory:

inline fun <reified T : Any> proxyReified(creator: () -> T): () -> T = throw IllegalStateException()
inline fun <reified T : Any> nullableProxyReified(creator: () -> T?): () -> T? = throw IllegalStateException()
inline fun <reified T : Any, U> twiceProxyReified(creator: () -> U): () -> U = throw IllegalStateException()

fun <T> proxyParameter(interfaceClass: Class<T>, creator: () -> T): () -> T = throw IllegalStateException()
fun <T> nullableProxyParameter(interfaceClass: Class<T>, creator: () -> T?): () -> T? = throw IllegalStateException()
fun <T : Any, U> twiceProxyParameter(interfaceClass: Class<T>, creator: () -> U): () -> U = throw IllegalStateException()

fun proxyTest() {
    // ERROR: Violates upper bound "Any"
    val reifiedNullable: () -> Runnable? = proxyReified<Runnable> { null }
    // Correct
    val reifiedNonNullable: () -> Runnable = proxyReified { Runnable { } }

    // Correct
    val nullableReifiedNullable: () -> Runnable? = nullableProxyReified<Runnable> { null }
    // ERROR: Does not inherit nullability from passed-in lambda
    val nullableReifiedNonNullable: () -> Runnable = nullableProxyReified { Runnable { } }

    // UNDESIRED: Allows user to make a mistake: "Closeable" is not assignable to "Runnable"
    val twiceReifiedNullable: () -> Closeable? = twiceProxyReified<Runnable, Closeable?>({ null })
    // UNDESIRED: Allows user to make a mistake: "Closeable" is not assignable to "Runnable"
    val twiceReifiedNonNullable: () -> Closeable = twiceProxyReified<Runnable, Closeable>({ Closeable {  } })

    // ERROR: Cannot get the class of a nullable type
    val parameterNullable: () -> Runnable? = proxyParameter<Runnable?>(Runnable::class.java, { null })
    // Correct
    val parameterNonNullable: () -> Runnable = proxyParameter(Runnable::class.java, { Runnable { } })

    // Correct
    val nullableParameterNullable: () -> Runnable? = nullableProxyParameter(Runnable::class.java, { null })
    // ERROR: Does not inherit nullability from passed-in lambda
    val nullableParameterNonNullable: () -> Runnable = nullableProxyParameter(Runnable::class.java, { Runnable { } })

    // UNDESIRED: Allows user to make a mistake: "Closeable" is not assignable to "Runnable"
    val twiceParameterNullable: () -> Closeable? = twiceProxyParameter(Runnable::class.java, { null })
    // UNDESIRED: Allows user to make a mistake: "Closeable" is not assignable to "Runnable"
    val twiceParameterNonNullable: () -> Closeable = twiceProxyParameter(Runnable::class.java, { Closeable {  } })
}

I think the function variants with two type parameters are pretty close to what I want, but I cannot specify that one must always be the non-nullable variant of the (possibly nullable) other. I tried it with two upper bounds using where, but you can’t combine actual types with a type parameter:

inline fun <reified T, U> twiceUpperBoundProxyReified(creator: () -> U): () -> U 
        where T: Any, T: U = throw IllegalStateException()

Is there any way to do what I want? I don’t mind an unchecked cast in this case, if the cast will always succeed.


#2

After some detours I finally got what I wanted. It turns out that you can always cast KClass<T> to KClass<Any>. After that you can get the Java class of the type parameter from the Kotlin class.

The casts are unchecked of course, so if you don’t want code warnings you need to add suppressions:

inline fun <reified TActualInterface : TInterface> weave(wrappedDefinition: Definition<TActualInterface>): Definition<TActualInterface> {
    @Suppress("UNCHECKED_CAST")
    val actualInterfaceClassAsAnyClass = TActualInterface::class as KClass<Any>
    @Suppress("UNCHECKED_CAST")
    val actualInterfaceJavaClass = actualInterfaceClassAsAnyClass.java as Class<TActualInterface>
    return weave(actualInterfaceJavaClass, wrappedDefinition)
}

Here are some examples of its use:

val someService: Definition<SomeService?> = ...
val transactionalSomeService: Definition<SomeService?> = transactional.weave(someService)

val anotherService: Definition<AnotherService> = ...
val transactionalAnotherService: Definition<AnotherService> = transactional.weave(anotherService)

Here are some statements that result in compiler errors:

val someService: Definition<SomeService?> = ...
// Cannot convert nullable to non-nullable type
val transactionalSomeService: Definition<SomeService> = transactional.weave(someService)
// Cannot convert "SomeService?" to "AnotherService?"
val transactionalAnotherService1: Definition<AnotherService?> = transactional.weave(someService)
// Not even if you try to force it somewhere else
val transactionalAnotherService2: Definition<AnotherService?> = transactional.weave<AnotherService?>(someService)