Kotlin - reflection and type safety - kotlin

I am writing a small library for programatically generating SQL queries.
The goal is that the API of this library can be used like that:
myQuery.where(
MyClass::id equal "foo",
MyClass::age notEqual 55
).findAll()
The signatures of both the where function, and the infix operators is under my control. Here are the relevant definitions that I have at the moment:
interface KpaFilter<BASE, FIELD>
infix fun <BASE: Any, FIELD: Any> KProperty1<BASE, FIELD>.equal(value: FIELD): KpaFilter<BASE, FIELD> { ... }
fun <BASE: Any> where(vararg filters: KpaFilter<BASE, *>?): KpaQuery<BASE>
Still, I cannot find a proper way to make this type safe. For example, I would like this to raise a compilation error, but unfortunately, it compiles:
val someFilter = MyClass::id equal 55 // id is a string
Is it possible to somehow modify the signatures of the declarations above and achieve this kind of type safety, without making the API more cumbersome than its current form?

If you don't mind using Kotlin internal APIs, there is a hidden feature in the compiler which does exactly this. The feature is enabled by annotating a parameter with #kotlin.internal.Exact:
#Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
infix fun <BASE: Any, FIELD: Any> KProperty1<BASE, #kotlin.internal.Exact FIELD>.equal(value: FIELD): KpaFilter<BASE, FIELD> { ... }
This works pretty well and can be used in regular Kotlin projects. Of course, we don't have guarantees it will be supported in the future.

Related

Same type for receiver and argument in Kotlin function

Is there any difference between these two Kotlin extension functions?
fun Any?.f(o: Any?) = 100
fun <T> T.g(o: T) = 100
Is it possible to rewrite g in such a way that the type of its argument and receiver are forced to be the same?
That is, 10.g(5) and "x".g("y") are OK, but 10.g("y") does not compile.
Edit:
Given this, I guess the answer to my second question is no, uless one adds additional arguments.
I believe this is not possible officially at the time of writing this answer (Kotlin 1.7.20).
However, internally Kotlin compiler supports such case, it allows to change the default behavior and use exact type parameters. This is controlled by the internal #Exact annotation and it is used in many places across the Kotlin stdlib.
With some hacking we can enable this behavior in our own code:
#Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
fun <T> #kotlin.internal.Exact T.g(o: #kotlin.internal.Exact T) = 100
Of course, this is purely a hack and it may stop working in future versions of Kotlin.
Update
Answering your first question on whether there is a difference between using Any and T. Generic functions make the most sense if the type parameter is not only consumed, but also passed somewhere further. For example, if the function returns T or it receives an object that consumes T:
fun main() {
var result = 5.g(7)
}
fun <T> T.g(o: T): T = if (...) this else o
In this case result is of type Int. If we use Any instead of T, result would have to be Any as well.

Generic Parameter Requires type "Nothing" when T is type "Any"

I am a new Kotlin programmer, and I am having a problem relating to generics
In the following code, I am getting an error with it.get(this) "Type mismatch.
Required: Nothing. Found: Any.
inline fun <reified T : Any> Any.getPropertiesWithType() =
this::class.memberProperties
.filter { it.returnType.isSubtypeOf(T::class.starProjectedType) }
.map {
it.isAccessible = true
it.get(this) as T
}
This is confusing because memberProperties returns a Collection<KProperty1<T, *>> where T is the type of this, which is Any in my case. KProperty1.get() takes one argument of that same type T, so I would expect Any could be passed without an issue.
One thing I noticed is that it in the filter and map are both type KProperty<out Any, *>> but memberProperties does not have the out variance.
If I replace this::class with this.javaClass.kotlin, it works without an error, but that seems like a very bad way to go about this.
If anyone knows a way to fix this, or an entirely different strategy, it would be much appreciated. I am new to Kotlin and still do things in the Java way sometimes.
this::class is analogous to this.getClass() in Java, which returns a KClass<out Any> (Class<? extends Object> in Java). After all, just because the type of this is Any, its runtime type may not be Any - it could be any subclass of Any. out Any captures this idea.
Therefore, the memberProperties of this::class will be a collection of KProperty1<out Any, *>.
In the same way that you cannot add anything to a MutableList<out Any> using add, you cannot get the value of a KProperty1<out Any, *> - an input parameter's type is marked out. Another way to think about this is that the property could be on any subclass of Any, trying to get the property using this, which is just an Any, obviously doesn't work.
I would move the unchecked cast to it:
(it as KProperty1<Any, T>).get(this)
As for why .javaClass works, it is because javaClass actually gives you a Class<Any>, not Class<out Any>. IMO, this doesn't make much sense, but it is what it is.

Kotlin compiler shows compilation error and suggests to "change type from 'T' to 'T"' while there is only one T in the context

I tried to implement some type classes from Haskell but confronted the issue that is probably a bug in the Kotlin compiler.
interface Semigroup<Instance> {
infix fun Instance.assocOp(oother: Instance): Instance
}
inline fun <reified T: Semigroup<T>> Iterable<T>.concat() = this.reduce<T, T> { acc: T, t: T -> acc.assocOp(t) }
The error message is "Expected parameter of type T".
IDEA suggests to "Change type from 'T' to 'T'" (does nothing).
I expect acc to belong to the type T mentioned in generics. But because of some reason compiler tries to find some other type T. I tried to
specify the type explicitly/implicitly
build ignoring IDEA message
change used version of Kotlin compiler (I have tried 1.4.20, 1.4.10, 1.3.72).
Nothing worked.
I suppose that writing the function without reduce (manually) may help to deal with it. Also, writing java code doing the same may help to mitigate the problem. But these solutions are only workarounds for the problem. Is the issue my fault or the compiler bug?
The compiler error clearly is not helpful here. However, it is correct that the code should not compile IMO.
You're defining the method assocOp as a member extension function. The extension applies to any type T, but it's a member of the interface Semigroup<T>.
To call that extension, you need both a receiver or type T and a receiver of type Semigroup<T> (acting as a context).
In your case, the type T both plays the role of the generic type parameter and of the Semigroup<T>, but you still need to have 2 "receivers" for your extension, even if both are the same instance.
Maybe try this:
inline fun <reified T : Semigroup<T>> Iterable<T>.concat(): T =
reduce<T, T> { t1: T, t2: T -> with(t1) { t1.assocOp(t2) } }
The with(t1) { ... } provides a context of type Semigroup<T>, while the t1 used in t1.assocOp(t2) acts as the T receiver.

Kotlin thinks that two methods have the same JVM signature, but the actually don't

I came from the C# background and I know how to implement this in C#, but I'm struggling with Kotlin.
I've got 2 extension functions:
fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>
and
fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>
Clearly, the return type of func is different in both functions. The first function executes it and returns this, the second executes func and returns the result of func.
But it gives me an error:
"Platform declaration clash: The following declarations have the same JVM signature".
How to implement this correctly in Kotlin?
Your functions have a conflicting signature within the JVM due to type erasure (the internal Function0<T> class being used to represent the function parameters); and you can fix this by giving each of them a JVM specific name. From Kotlin you would still access them by the original name, but from Java or internally another name is actually used. Simply use the #JvmName annotation on the alternative versions:
fun <T> Foo<T>.myFunction(func: () -> Unit): Foo<T>
#JvmName("myfunctionWithFoo")
fun <T> Foo<T>.myFunction(func: () -> Foo<T>): Foo<T>
On the JVM, we have to contend with type erasure. Meaning essentially that the types (T in this case) are thrown away in the compiled bytecode and that required checks are only done at compile time. Given that, you have to look at your function declaration with that in mind.
Kotlin will define your function argument as a Function0 in both cases. Because the types are erased, () -> Unit and () -> Foo<T> both look the same in the bytecode. We can prove this out by decompiling the code you've provided (I renamed one of these myFunction2 to get this to work):
public final class com/ginsberg/KotlinStuffKt {
public final static myFunction(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;
public final static myFunction2(Lcom/ginsberg/Foo;Lkotlin/jvm/functions/Function0;)Lcom/ginsberg/Foo;
}
This is what the Kotlin compiler is generating (it does more, but I have removed the non-essential parts from this example). As you can see, our types are gone thanks to type erasure. And if we undo my change (myFunction2 becomes myFunction), there's no way at all to tell these apart. This is what the compiler is complaining about - if you erase the types the JVM can't tell these functions apart.

Is there a better way to write CompletableFutrue.XXXasync() invocations in kotlin?

Java CompletableFuture<T> has a lot of async methods, static or instance, in this format
public <U> CompletableFuture<U> XXXasync(SomeFunctionalInterface<T> something, Executor executor)
If you have enough experience with FP in kotlin, you will immediately realize these function are extremely awkward to use in kotlin, because the SAM interface is not the last parameter.
aCompletableFutrue.thenComposeAsync(Function<SomeType, CompletableFuture<SomeOtherType>> {
// ^ WHAT A LONG TYPE NAME THAT NEED TO BE HAND WRITTEN
// do something that has to be written in multiple lines.
// for that sake of simplicity I use convert() to represent this process
convert(it)
}, executor)
That Function has a very very long generic signature that I don't know how to let IDE generate. It will be a plain in the butt if the type name become even longer or contains a ParameterizedType or has type variance annotations.
It also looks nasty because of the trailing , executor) on line 5.
Is there some missing functionality in kotlin or IDE that can help with the situation? At least I don't want to write that long SAM constructor all by myself.
Rejected solutions:
Using named parameter doesn't seem to work because this feature only works on a kotlin function.
Abandon async methods sounds bad from the very beginning.
Kotlin corountine is rejected because we are working with some silly Java libraries that accept CompletionStage only.
IF you calling the api from java that takes a functional interface parameter at last, you can just using lambda in kotlin.
val composed: CompletableFuture<String> = aCompletableFutrue.thenComposeAsync {
CompletableFuture.supplyAsync { it.toString() }
};
Secondly, if you don't like the java api method signature. you can write your own extension methods, for example:
fun <T, U> CompletableFuture<T>.thenComposeAsync(executor: Executor
, mapping: Function1<in T, out CompletionStage<U>>): CompletableFuture<U> {
return thenComposeAsync(Function<T,CompletionStage<U>>{mapping(it)}, executor)
}
THEN you can makes the lambda along the method.
aCompletableFutrue.thenComposeAsync(executor){
// do working
}