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.