Type inference incorrect w.r.t. variance
There are some errors in the type inference of KTypes in DataFrame that make it behave differently compared to the Kotlin Compiler.
For instance, given
interface AbstractType<T>
listOf<AbstractType<Int>>() + listOf<AbstractType<Any>>() // == List<AbstractType<out Any>>
whereas DF would make it List<AbstractType<Any>>, ignoring the out-variance.
Similarly,
listOf<List<Int>>() + listOf<List<List<Any>>>() // == List<List<Any>>
whereas DF would make it List<List<out Any>>, adding an out-variance which is not necessary.
DataFrame is the most incorrect is with in-variance:
listOf<AbstractType<in Int>>() + listOf<AbstractType<Any>>() // == List<AbstractType<in Int>>
yet, DF reports this as List<AbstractType<Any>>, which is just wrong.
This behavior is mostly regulated by Iterable<KType?>.commonType() and Iterable<KType>.commonTypeListifyValues().
We need proper KType unification which takes variance into account when merging types respecting the Kotlin Specification.
Specifically, given two KTypeProjections, we need to be able to give the union/least upper bound/OR, and the intersection/greatest lower bound/AND. I don't know if there already exists a library that provides this logic, as understanding this part of the spec and recreating it is difficult to say the least.
The only similar place where this is done is actually inside the Kotlin compiler itself using ConeKotlinType and ConeTypeProjection instead of KType and KTypeProjection, but I'm not sure this can be reused.
Small start: the common child of two classes can be found like this:
/** @include [commonChild] */
internal fun commonChild(vararg classes: KClass<*>): KClass<*> = commonChild(classes.toList())
/**
* Given the classes in [classes] and their sealed subclasses, this function
* returns the single class in that collection that is a child of all the other classes in it.
* If no such class exists, [KClass]<[Nothing]> will be returned.
*
* It might be the case that there exists a common child of all given classes outside of this collection, but
* since we cannot query non-sealed subclasses (just supertypes) of classes, we can only return classes from
* [classes] and their sealed subclasses (and [KClass]<[Nothing]>).
*
* This is useful for combining in-variance Types, e.g. given `Function1<in T, out R>`; combining `Function1<Int, Unit>`
* and `Function1<Any, Unit>` becomes `Function1<Int, Unit>`, since `Int` is the only input type that can be validly given
* to both instances of `Function1`
*/
internal fun commonChild(classes: Iterable<KClass<*>>): KClass<*> {
val kClasses = classes.distinct()
if (kClasses.size == 1) return kClasses.single()
for (klass in kClasses) {
if (kClasses.all { it == klass || it.isSuperclassOf(klass) })
return klass
}
val subKClasses = kClasses
.flatMap { it.sealedSubclasses }
.distinct()
val allKClasses = (kClasses + subKClasses).distinct()
for (klass in subKClasses) {
if (allKClasses.all { it == klass || it.isSuperclassOf(klass) })
return klass
}
return Nothing::class
}