Skip to content

Commit 0cedc78

Browse files
committed
Substitute type parameters with type arguments in toDataFrame conversion
1 parent 33e2e48 commit 0cedc78

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

  • core/src
    • main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api
    • test/kotlin/org/jetbrains/kotlinx/dataframe/api

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/toDataFrame.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.jetbrains.kotlinx.dataframe.impl.isGetterLike
2222
import org.jetbrains.kotlinx.dataframe.impl.isJavaRecord
2323
import org.jetbrains.kotlinx.dataframe.impl.projectUpTo
2424
import org.jetbrains.kotlinx.dataframe.impl.recordComponentNames
25+
import org.jetbrains.kotlinx.dataframe.impl.replace
2526
import org.jetbrains.kotlinx.dataframe.impl.schema.sortWithConstructor
2627
import java.lang.reflect.InvocationTargetException
2728
import java.lang.reflect.Method
@@ -31,13 +32,13 @@ import kotlin.reflect.KCallable
3132
import kotlin.reflect.KClass
3233
import kotlin.reflect.KProperty
3334
import kotlin.reflect.KType
35+
import kotlin.reflect.KTypeParameter
3436
import kotlin.reflect.KVisibility
3537
import kotlin.reflect.full.isSubclassOf
3638
import kotlin.reflect.full.memberFunctions
3739
import kotlin.reflect.full.memberProperties
3840
import kotlin.reflect.full.primaryConstructor
3941
import kotlin.reflect.full.starProjectedType
40-
import kotlin.reflect.full.valueParameters
4142
import kotlin.reflect.full.withNullability
4243
import kotlin.reflect.jvm.isAccessible
4344
import kotlin.reflect.jvm.javaField
@@ -237,6 +238,8 @@ internal fun convertToDataFrame(
237238
return dataFrameOf(column)
238239
}
239240

241+
val substitution: Map<KTypeParameter, KType> = type.typeParametersSubstitution()
242+
240243
val properties: List<KCallable<*>> = roots
241244
.ifEmpty {
242245
clazz.properties()
@@ -307,7 +310,7 @@ internal fun convertToDataFrame(
307310
}
308311
}
309312

310-
val returnType = property.returnType.let { type ->
313+
val returnType = property.returnType.replace(substitution).let { type ->
311314
if (type.classifier is KClass<*>) {
312315
type
313316
} else {
@@ -429,6 +432,13 @@ internal fun convertToDataFrame(
429432

430433
private fun KType.classifierOrAny(): KClass<*> = classifier as? KClass<*> ?: Any::class
431434

435+
internal fun KType.typeParametersSubstitution(): Map<KTypeParameter, KType> {
436+
val klass = classifier as? KClass<*> ?: return emptyMap()
437+
return klass.typeParameters.zip(arguments).mapNotNull { (param, projection) ->
438+
projection.type?.let { param to it }
439+
}.toMap()
440+
}
441+
432442
private fun KClass<*>.properties(): List<KCallable<*>> {
433443
// fall back to getter functions for pojo-like classes if no member properties were found
434444
return memberProperties

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/unfold.kt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package org.jetbrains.kotlinx.dataframe.api
22

3+
import io.kotest.assertions.asClue
34
import io.kotest.matchers.shouldBe
45
import io.kotest.matchers.types.shouldBeInstanceOf
56
import org.jetbrains.kotlinx.dataframe.AnyFrame
7+
import org.jetbrains.kotlinx.dataframe.DataColumn
8+
import org.jetbrains.kotlinx.dataframe.DataFrame
9+
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
10+
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
11+
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
612
import org.junit.Test
713
import kotlin.reflect.typeOf
814

@@ -65,4 +71,66 @@ class UnfoldTests {
6571
)
6672

6773
data class Group(val id: String, val participants: List<Person>)
74+
75+
@Test
76+
fun `unfold pair of dataframe structures`() {
77+
val schema = dataFrameOf("b" to columnOf(42)).cast<SimpleDataSchema>()
78+
79+
val df = dataFrameOf("pairs" to columnOf(schema to schema.first()))
80+
.unfold("pairs")
81+
82+
df.schema().asClue {
83+
val pairsGroup = df.shouldHaveColumnGroup("pairs")
84+
pairsGroup.shouldHaveFrameColumn("first") {
85+
it[0].shouldHaveColumn<Int>("b")
86+
}
87+
pairsGroup.shouldHaveColumnGroup("second") {
88+
it.shouldHaveColumn<Int>("b")
89+
}
90+
}
91+
}
92+
93+
@DataSchema
94+
data class SimpleDataSchema(val b: Int)
95+
96+
@Test
97+
fun `unfold pair of dataschema object structures`() {
98+
val element = SimpleDataSchema(42)
99+
val df = dataFrameOf("pairs" to columnOf(listOf(element) to element))
100+
.unfold("pairs")
101+
102+
df.schema().asClue {
103+
val pairsGroup = df.shouldHaveColumnGroup("pairs")
104+
pairsGroup.shouldHaveFrameColumn("first") {
105+
it[0].shouldHaveColumn<Int>("b")
106+
}
107+
pairsGroup.shouldHaveColumnGroup("second") {
108+
it.shouldHaveColumn<Int>("b")
109+
}
110+
}
111+
}
112+
113+
fun DataFrame<*>.shouldHaveColumnGroup(
114+
name: String,
115+
block: (ColumnGroup<*>) -> Unit = {
116+
},
117+
): ColumnGroup<*> = getColumnOrNull(name).shouldBeInstanceOf<ColumnGroup<*>>(block)
118+
119+
fun DataFrame<*>.shouldHaveFrameColumn(
120+
name: String,
121+
block: (FrameColumn<*>) -> Unit = {
122+
},
123+
): FrameColumn<*> = getColumnOrNull(name).shouldBeInstanceOf<FrameColumn<*>>(block)
124+
125+
inline fun <reified T> DataFrame<*>.shouldHaveColumn(
126+
name: String,
127+
block: (DataColumn<T>) -> Unit = {
128+
},
129+
): DataColumn<T> {
130+
val shouldBeInstanceOf = getColumnOrNull(name).shouldBeInstanceOf<DataColumn<*>>()
131+
shouldBeInstanceOf.type() shouldBe typeOf<T>()
132+
val cast = shouldBeInstanceOf.cast<T>()
133+
block(cast)
134+
return cast
135+
}
68136
}

0 commit comments

Comments
 (0)