Skip to content

Commit 23afc79

Browse files
authored
Merge pull request #193 from Kotlin/jupyter-inherit-from-dataclass-bug
fix for "Jupyter codegen: attempt to inherit from data class"
2 parents a87b440 + 3396425 commit 23afc79

File tree

8 files changed

+143
-39
lines changed

8 files changed

+143
-39
lines changed

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/MarkersExtractor.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ internal object MarkersExtractor {
3737
fun get(markerClass: KClass<*>, nullableProperties: Boolean = false): Marker =
3838
cache.getOrPut(Pair(markerClass, nullableProperties)) {
3939
val fields = getFields(markerClass, nullableProperties)
40-
val isOpen = markerClass.findAnnotation<DataSchema>()?.isOpen ?: false
40+
val isOpen = !markerClass.isSealed &&
41+
markerClass.java.isInterface &&
42+
markerClass.findAnnotation<DataSchema>()?.isOpen == true
43+
4144
val baseSchemas = markerClass.superclasses.filter { it != Any::class }.map { get(it, nullableProperties) }
4245
Marker(
4346
name = markerClass.qualifiedName ?: markerClass.simpleName!!,

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/ReplCodeGenerator.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@ import org.jetbrains.kotlinx.dataframe.codeGen.CodeWithConverter
66
import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGeneratorImpl
77
import org.jetbrains.kotlinx.jupyter.api.Code
88
import kotlin.reflect.KClass
9+
import kotlin.reflect.KMutableProperty
910
import kotlin.reflect.KProperty
1011

1112
internal interface ReplCodeGenerator {
1213

13-
fun process(df: AnyFrame, property: KProperty<*>? = null): CodeWithConverter
14+
fun process(
15+
df: AnyFrame,
16+
property: KProperty<*>? = null,
17+
isMutable: Boolean = property is KMutableProperty?,
18+
): CodeWithConverter
1419

15-
fun process(row: AnyRow, property: KProperty<*>? = null): CodeWithConverter
20+
fun process(
21+
row: AnyRow,
22+
property: KProperty<*>? = null,
23+
isMutable: Boolean = property is KMutableProperty?,
24+
): CodeWithConverter
1625

1726
fun process(markerClass: KClass<*>): Code
1827

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/generateCode.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import org.jetbrains.dataframe.impl.codeGen.CodeGenerator
44
import org.jetbrains.kotlinx.dataframe.DataFrame
55
import org.jetbrains.kotlinx.dataframe.api.schema
66

7-
public inline fun <reified T> DataFrame<T>.generateCode(fields: Boolean = true, extensionProperties: Boolean = true): String {
7+
public inline fun <reified T> DataFrame<T>.generateCode(
8+
fields: Boolean = true,
9+
extensionProperties: Boolean = true,
10+
): String {
811
val name = if (T::class.isAbstract) {
912
T::class.simpleName!!
1013
} else "DataEntry"
@@ -15,16 +18,16 @@ public fun <T> DataFrame<T>.generateCode(
1518
markerName: String,
1619
fields: Boolean = true,
1720
extensionProperties: Boolean = true,
18-
visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC
21+
visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC,
1922
): String {
2023
val codeGen = CodeGenerator.create()
2124
return codeGen.generate(
22-
schema(),
23-
markerName,
25+
schema = schema(),
26+
name = markerName,
2427
fields = fields,
2528
extensionProperties = extensionProperties,
2629
isOpen = true,
27-
visibility
30+
visibility = visibility,
2831
).code.declarations
2932
}
3033

@@ -34,7 +37,7 @@ public inline fun <reified T> DataFrame<T>.generateInterfaces(): String = genera
3437
)
3538

3639
public fun <T> DataFrame<T>.generateInterfaces(markerName: String): String = generateCode(
37-
markerName,
40+
markerName = markerName,
3841
fields = true,
3942
extensionProperties = false
4043
)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/CodeGeneratorImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ import org.jetbrains.kotlinx.jupyter.api.Code
3737

3838
private fun renderNullability(nullable: Boolean) = if (nullable) "?" else ""
3939

40-
internal fun getRequiredMarkers(schema: DataFrameSchema, markers: Iterable<Marker>) = markers
41-
.filter { it.isOpen && it.schema.compare(schema).isSuperOrEqual() }
40+
internal fun Iterable<Marker>.filterRequiredForSchema(schema: DataFrameSchema) =
41+
filter { it.isOpen && it.schema.compare(schema).isSuperOrEqual() }
4242

4343
internal val charsToQuote = """[ `(){}\[\].<>'"/|\\!?@:;%^&*#$-]""".toRegex()
4444

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/ReplCodeGeneratorImpl.kt

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import kotlin.reflect.KClass
1919
import kotlin.reflect.KMutableProperty
2020
import kotlin.reflect.KProperty
2121
import kotlin.reflect.KType
22+
import kotlin.reflect.KVisibility
2223
import kotlin.reflect.full.findAnnotation
2324
import kotlin.reflect.full.superclasses
2425
import kotlin.reflect.jvm.jvmErasure
@@ -44,11 +45,12 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
4445
else -> null
4546
}
4647

47-
override fun process(row: AnyRow, property: KProperty<*>?) = process(row.df(), property)
48+
override fun process(row: AnyRow, property: KProperty<*>?, isMutable: Boolean) =
49+
process(row.df(), property, isMutable)
4850

49-
override fun process(df: AnyFrame, property: KProperty<*>?): CodeWithConverter {
51+
override fun process(df: AnyFrame, property: KProperty<*>?, isMutable: Boolean): CodeWithConverter {
5052
var targetSchema = df.schema()
51-
var isMutable = false
53+
var isMutable = isMutable
5254

5355
if (property != null) {
5456
val wasProcessedBefore = property in registeredProperties
@@ -66,8 +68,7 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
6668
// for mutable properties we do strong typing only at the first processing, after that we allow its type to be more general than actual data frame type
6769
if (wasProcessedBefore || columnSchema == targetSchema) {
6870
// property scheme is valid for current data frame, but we should also check that all compatible open markers are implemented by it
69-
val requiredBaseMarkers =
70-
getRequiredMarkers(columnSchema, registeredMarkers.values)
71+
val requiredBaseMarkers = registeredMarkers.values.filterRequiredForSchema(columnSchema)
7172
if (requiredBaseMarkers.any() && requiredBaseMarkers.all { currentMarker.implements(it) }) {
7273
return CodeWithConverter.Empty
7374
}
@@ -78,7 +79,7 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
7879
}
7980
}
8081

81-
return generate(targetSchema, markerInterfacePrefix, isMutable)
82+
return generate(schema = targetSchema, name = markerInterfacePrefix, isOpen = isMutable)
8283
}
8384

8485
fun generate(
@@ -87,13 +88,15 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
8788
isOpen: Boolean,
8889
): CodeWithConverter {
8990
val result = generator.generate(
90-
schema,
91-
name,
91+
schema = schema,
92+
name = name,
9293
fields = false,
9394
extensionProperties = true,
94-
isOpen,
95-
MarkerVisibility.IMPLICIT_PUBLIC,
96-
registeredMarkers.values
95+
isOpen = isOpen,
96+
visibility = MarkerVisibility.IMPLICIT_PUBLIC,
97+
knownMarkers = registeredMarkers
98+
.filterKeys { it.visibility != KVisibility.PRIVATE }
99+
.values,
97100
)
98101

99102
result.newMarkers.forEach {
@@ -121,12 +124,12 @@ internal class ReplCodeGeneratorImpl : ReplCodeGenerator {
121124
if (baseClassNames == tempBaseClassNames) {
122125
val newBaseMarkers = baseClasses.map { resolve(it) }
123126
val newMarker = Marker(
124-
clazz.qualifiedName!!,
125-
temp.isOpen,
126-
temp.fields,
127-
newBaseMarkers,
128-
MarkerVisibility.IMPLICIT_PUBLIC,
129-
clazz
127+
name = clazz.qualifiedName!!,
128+
isOpen = temp.isOpen,
129+
fields = temp.fields,
130+
superMarkers = newBaseMarkers,
131+
visibility = MarkerVisibility.IMPLICIT_PUBLIC,
132+
klass = clazz,
130133
)
131134
registeredMarkers[markerClass] = newMarker
132135
generatedMarkers.remove(temp.name)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/codeGen/SchemaProcessorImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal class SchemaProcessorImpl(
2222
override val generatedMarkers = mutableListOf<Marker>()
2323

2424
private fun DataFrameSchema.getAllSuperMarkers() = registeredMarkers
25-
.filter { it.schema.compare(this).isSuperOrEqual() }
25+
.filter { it.isOpen && it.schema.compare(this).isSuperOrEqual() }
2626

2727
private fun List<Marker>.onlyLeafs(): List<Marker> {
2828
val skip = flatMap { it.allSuperMarkers.keys }.toSet()
@@ -133,7 +133,7 @@ internal class SchemaProcessorImpl(
133133
return Marker(name, isOpen, fields, baseMarkers.onlyLeafs(), visibility, emptyList(), emptyList())
134134
}
135135

136-
private fun DataFrameSchema.getRequiredMarkers() = getRequiredMarkers(this, registeredMarkers)
136+
private fun DataFrameSchema.getRequiredMarkers() = registeredMarkers.filterRequiredForSchema(this)
137137

138138
override fun process(
139139
schema: DataFrameSchema,

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/ReplCodeGenTests.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class ReplCodeGenTests : BaseTest() {
2626
val stringName = String::class.simpleName!!
2727

2828
class Test1 {
29-
@DataSchema(isOpen = false)
29+
@DataSchema
3030
interface _DataFrameType
3131

3232
@DataSchema(isOpen = false)
@@ -37,10 +37,10 @@ class ReplCodeGenTests : BaseTest() {
3737
}
3838

3939
class Test2 {
40-
@DataSchema(isOpen = false)
40+
@DataSchema
4141
interface _DataFrameType
4242

43-
@DataSchema(isOpen = false)
43+
@DataSchema
4444
interface _DataFrameType1
4545

4646
@DataSchema(isOpen = false)
@@ -68,13 +68,13 @@ class ReplCodeGenTests : BaseTest() {
6868
@Test
6969
fun `process derived markers`() {
7070
val repl = ReplCodeGenerator.create()
71-
val code = repl.process(df).declarations
71+
val code = repl.process(df, isMutable = true).declarations
7272

7373
val marker = ReplCodeGeneratorImpl.markerInterfacePrefix
7474
val markerFull = Test1._DataFrameType::class.qualifiedName!!
7575

7676
val expected = """
77-
@DataSchema(isOpen = false)
77+
@DataSchema
7878
interface $marker
7979
8080
val $dfName<$marker>.age: $dataCol<$intName> @JvmName("${marker}_age") get() = this["age"] as $dataCol<$intName>
@@ -100,7 +100,7 @@ class ReplCodeGenTests : BaseTest() {
100100
code2 shouldBe ""
101101

102102
val df3 = typed.filter { city != null }
103-
val code3 = repl.process(df3).declarations
103+
val code3 = repl.process(df3, isMutable = false).declarations
104104
val marker3 = marker + "1"
105105
val expected3 = """
106106
@DataSchema(isOpen = false)
@@ -118,7 +118,7 @@ class ReplCodeGenTests : BaseTest() {
118118
code4 shouldBe ""
119119

120120
val df5 = typed.filter { weight != null }
121-
val code5 = repl.process(df5).declarations
121+
val code5 = repl.process(df5, isMutable = false).declarations
122122
val marker5 = marker + "2"
123123
val expected5 = """
124124
@DataSchema(isOpen = false)
@@ -149,7 +149,7 @@ class ReplCodeGenTests : BaseTest() {
149149
150150
""".trimIndent()
151151

152-
val code = repl.process(typed).declarations.trimIndent()
152+
val code = repl.process(typed, isMutable = false).declarations.trimIndent()
153153
code shouldBe expected
154154
}
155155

@@ -176,7 +176,7 @@ class ReplCodeGenTests : BaseTest() {
176176
val $dfRowName<$marker?>.weight: $intName? @JvmName("Nullable${marker}_weight") get() = this["weight"] as $intName?
177177
""".trimIndent()
178178

179-
val code = repl.process(typed).declarations.trimIndent()
179+
val code = repl.process(typed, isMutable = false).declarations.trimIndent()
180180
code shouldBe expected
181181
}
182182

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/JupyterCodegenTests.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.jetbrains.kotlinx.dataframe.jupyter
22

33
import io.kotest.assertions.throwables.shouldNotThrowAny
4+
import io.kotest.matchers.should
45
import io.kotest.matchers.shouldBe
56
import io.kotest.matchers.types.shouldBeInstanceOf
67
import org.intellij.lang.annotations.Language
78
import org.jetbrains.kotlinx.dataframe.AnyFrame
9+
import org.jetbrains.kotlinx.dataframe.api.isNotEmpty
810
import org.jetbrains.kotlinx.dataframe.columns.ValueColumn
911
import org.jetbrains.kotlinx.dataframe.type
1012
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResult
@@ -29,6 +31,90 @@ class JupyterCodegenTests : JupyterReplTestCase() {
2931
res2["value"].type shouldBe typeOf<List<Any?>>()
3032
}
3133

34+
@Test
35+
fun `Don't inherit from data class`() {
36+
@Language("kts")
37+
val res1 = exec(
38+
"""
39+
@DataSchema
40+
data class A(val a: Int)
41+
""".trimIndent()
42+
)
43+
44+
@Language("kts")
45+
val res2 = execRaw(
46+
"""
47+
val df = dataFrameOf("a", "b")(1, 2)
48+
df
49+
""".trimIndent()
50+
)
51+
52+
(res2 as AnyFrame).should { it.isNotEmpty() }
53+
}
54+
55+
@Test
56+
fun `Don't inherit from non open class`() {
57+
@Language("kts")
58+
val res1 = exec(
59+
"""
60+
@DataSchema
61+
class A(val a: Int)
62+
""".trimIndent()
63+
)
64+
65+
@Language("kts")
66+
val res2 = execRaw(
67+
"""
68+
val df = dataFrameOf("a", "b")(1, 2)
69+
df
70+
""".trimIndent()
71+
)
72+
73+
(res2 as AnyFrame).should { it.isNotEmpty() }
74+
}
75+
76+
@Test
77+
fun `Don't inherit from open class`() {
78+
@Language("kts")
79+
val res1 = exec(
80+
"""
81+
@DataSchema
82+
open class A(val a: Int)
83+
""".trimIndent()
84+
)
85+
86+
@Language("kts")
87+
val res2 = execRaw(
88+
"""
89+
val df = dataFrameOf("a", "b")(1, 2)
90+
df
91+
""".trimIndent()
92+
)
93+
94+
(res2 as AnyFrame).should { it.isNotEmpty() }
95+
}
96+
97+
@Test
98+
fun `Do inherit from open interface`() {
99+
@Language("kts")
100+
val res1 = exec(
101+
"""
102+
@DataSchema
103+
interface A { val a: Int }
104+
""".trimIndent()
105+
)
106+
107+
@Language("kts")
108+
val res2 = execRaw(
109+
"""
110+
val df = dataFrameOf("a", "b")(1, 2)
111+
df
112+
""".trimIndent()
113+
)
114+
115+
(res2 as AnyFrame).should { it.isNotEmpty() }
116+
}
117+
32118
@Test
33119
fun `codegen for enumerated frames`() {
34120
@Language("kts")

0 commit comments

Comments
 (0)