Skip to content

Commit 5cf90d6

Browse files
Automated commit of generated code
1 parent 919a0b0 commit 5cf90d6

File tree

9 files changed

+136
-37
lines changed

9 files changed

+136
-37
lines changed

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ internal interface ConvertDocs {
190190
* * [LocalDateTime], [LocalDate], [LocalTime],
191191
* `Instant` ([kotlinx.datetime][DeprecatedInstant], [kotlin.time][StdlibInstant], and [java.time]),
192192
* * [URL], [IMG], [IFRAME].
193+
*
194+
* __NOTE__: Conversion between [Int] and [Char] is done by UTF-16 [Char.code].
195+
* To convert [Char]->[Int] the way it is written, use [parse()][parse] instead, or,
196+
* in either case, use [String] as intermediary type.
193197
*/
194198
interface SupportedTypes
195199

@@ -501,7 +505,7 @@ public class Convert<T, out C>(
501505
* preserving their original names and positions within the [DataFrame].
502506
*
503507
* The target type is provided as a reified type argument.
504-
* For the full list of supported types, see [ConvertDocs.SupportedTypes].
508+
* For the full list of supported types, see [SupportedTypes][ConvertDocs.SupportedTypes].
505509
*
506510
* For more information: [See `convert` on the documentation website.](https://kotlin.github.io/dataframe/convert.html)
507511
*
@@ -529,7 +533,7 @@ public class Convert<T, out C>(
529533
* preserving their original names and positions within the [DataFrame].
530534
*
531535
* The target type is provided as a [KType].
532-
* For the full list of supported types, see [ConvertDocs.SupportedTypes].
536+
* For the full list of supported types, see [SupportedTypes][ConvertDocs.SupportedTypes].
533537
*
534538
* For more information: [See `convert` on the documentation website.](https://kotlin.github.io/dataframe/convert.html)
535539
*
@@ -687,7 +691,7 @@ public inline fun <T, C, reified R> Convert<T, C>.perRowCol(
687691
*
688692
* The target type is provided as a reified type argument.
689693
*
690-
* For the full list of supported types, see [ConvertDocs.SupportedTypes].
694+
* For the full list of supported types, see [SupportedTypes][ConvertDocs.SupportedTypes].
691695
*
692696
* @param [C] The target type to convert values to.
693697
* @return A new [DataColumn] with the values converted to type [C].
@@ -697,7 +701,7 @@ public inline fun <reified C> AnyCol.convertTo(): DataColumn<C> = convertTo(type
697701
/**
698702
* Converts values in this column to the specified [type].
699703
*
700-
* For the full list of supported types, see [ConvertDocs.SupportedTypes].
704+
* For the full list of supported types, see [SupportedTypes][ConvertDocs.SupportedTypes].
701705
*
702706
* @param type The target type, provided as a [KType], to convert values to.
703707
* @return A new [DataColumn] with the values converted to [type].

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class ConverterScope(public val fromType: KType, public val toSchema: Col
5252
* df.convertTo<SomeSchema> {
5353
* // defines how to convert Int? -> String
5454
* convert<Int?>().with { it?.toString() ?: "No input given" }
55-
* // defines how to convert String -> SomeType
55+
* // defines how to convert String/Char -> SomeType
5656
* parser { SomeType(it) }
5757
* // fill missing column `sum` with expression `a+b`
5858
* fill { sum }.with { a + b }
@@ -102,6 +102,10 @@ public fun <T, C> ConvertToFill<T, C>.with(expr: RowExpression<T, C>) {
102102

103103
/**
104104
* Defines how to convert `String` values into given type [C].
105+
*
106+
* This method is a shortcut for `convert<String>().with { }`.
107+
*
108+
* If no converter is defined for `Char` values, this converter will be used for them as well.
105109
*/
106110
public inline fun <reified C> ConvertSchemaDsl<*>.parser(noinline parser: (String) -> C): Unit =
107111
convert<String>().with(parser)

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/parse.kt

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ import org.jetbrains.kotlinx.dataframe.impl.api.StringParser
1212
import org.jetbrains.kotlinx.dataframe.impl.api.parseImpl
1313
import org.jetbrains.kotlinx.dataframe.impl.api.tryParseImpl
1414
import org.jetbrains.kotlinx.dataframe.impl.io.FastDoubleParser
15-
import org.jetbrains.kotlinx.dataframe.typeClass
1615
import org.jetbrains.kotlinx.dataframe.util.DEPRECATED_ACCESS_API
1716
import org.jetbrains.kotlinx.dataframe.util.PARSER_OPTIONS
1817
import org.jetbrains.kotlinx.dataframe.util.PARSER_OPTIONS_COPY
1918
import java.time.format.DateTimeFormatter
2019
import java.util.Locale
2120
import kotlin.reflect.KProperty
2221
import kotlin.reflect.KType
23-
import kotlin.reflect.typeOf
2422
import kotlin.uuid.ExperimentalUuidApi
2523
import kotlin.uuid.Uuid
2624

@@ -327,13 +325,8 @@ public fun DataColumn<String?>.tryParse(options: ParserOptions? = null): DataCol
327325
* @return a new column with parsed values
328326
*/
329327
@JvmName("tryParseChar")
330-
public fun DataColumn<Char?>.tryParse(options: ParserOptions? = null): DataColumn<*> {
331-
// skip the Char parser, as we're trying to parse away from Char
332-
val providedSkipTypes = options?.skipTypes ?: DataFrame.parser.skipTypes
333-
val parserOptions = (options ?: ParserOptions()).copy(skipTypes = providedSkipTypes + typeOf<Char>())
334-
335-
return map { it?.toString() }.tryParse(parserOptions)
336-
}
328+
public fun DataColumn<Char?>.tryParse(options: ParserOptions? = null): DataColumn<*> =
329+
map { it?.toString() }.tryParseImpl(options)
337330

338331
public fun <T> DataFrame<T>.parse(options: ParserOptions? = null): DataFrame<T> =
339332
parse(options) {
@@ -356,15 +349,16 @@ public fun <T> DataFrame<T>.parse(options: ParserOptions? = null): DataFrame<T>
356349
* @return a new column with parsed values
357350
*/
358351
public fun DataColumn<String?>.parse(options: ParserOptions? = null): DataColumn<*> =
359-
tryParse(options).also { if (it.typeClass == String::class) error("Can't guess column type") }
352+
tryParse(options).also { if (it.isSubtypeOf<String?>()) error("Can't guess column type") }
360353

361354
/**
362355
* Tries to parse a column of chars as strings into a column of a different type.
363356
* Each parser in [Parsers] is run in order until a valid parser is found,
364357
* a.k.a. that parser was able to parse all values in the column successfully. If a parser
365358
* fails to parse any value, the next parser is tried.
366359
*
367-
* If all fail, the column is returned as `String`, this can never fail.
360+
* If all fail [IllegalStateException] is thrown. If you don't want this exception to be thrown,
361+
* use [tryParse] instead.
368362
*
369363
* Parsers that are [covered by][StringParser.coveredBy] other parsers are skipped.
370364
*
@@ -373,7 +367,9 @@ public fun DataColumn<String?>.parse(options: ParserOptions? = null): DataColumn
373367
*/
374368
@JvmName("parseChar")
375369
public fun DataColumn<Char?>.parse(options: ParserOptions? = null): DataColumn<*> =
376-
tryParse(options) // no need to throw an exception, as Char can always be parsed as String
370+
map { it?.toString() }
371+
.tryParse(options)
372+
.also { if (it.isSubtypeOf<Char?>() || it.isSubtypeOf<String?>()) error("Can't guess column type") }
377373

378374
@JvmName("parseAnyFrameNullable")
379375
public fun DataColumn<AnyFrame?>.parse(options: ParserOptions? = null): DataColumn<AnyFrame?> =

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ internal fun getConverter(from: KType, to: KType, options: ParserOptions? = null
230230

231231
internal typealias TypeConverter = (Any) -> Any?
232232

233+
private val TypeConverterIdentity: TypeConverter = { it }
234+
233235
internal fun Any.convertTo(type: KType): Any? {
234236
val clazz = javaClass.kotlin
235237
if (clazz.isSubclassOf(type.jvmErasure)) return this
@@ -242,6 +244,7 @@ internal inline fun <T> convert(crossinline converter: (T) -> Any?): TypeConvert
242244

243245
private enum class DummyEnum
244246

247+
@Suppress("UNCHECKED_CAST")
245248
internal fun createConverter(from: KType, to: KType, options: ParserOptions? = null): TypeConverter? {
246249
if (from.arguments.isNotEmpty() || to.arguments.isNotEmpty()) return null
247250
if (from.isMarkedNullable) {
@@ -250,25 +253,24 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n
250253
}
251254
val fromClass = from.jvmErasure
252255
val toClass = to.jvmErasure
256+
return when {
257+
fromClass == toClass -> TypeConverterIdentity
253258

254-
if (fromClass == toClass) return { it }
255-
256-
if (toClass.isValue) {
257-
val constructor =
258-
toClass.primaryConstructor ?: error("Value type $toClass doesn't have primary constructor")
259-
val underlyingType = constructor.parameters.single().type
260-
val converter = getConverter(from, underlyingType)
261-
?: throw TypeConverterNotFoundException(from, underlyingType, null)
262-
return convert<Any> {
263-
val converted = converter(it)
264-
if (converted == null && !underlyingType.isMarkedNullable) {
265-
throw TypeConversionException(it, from, underlyingType, null)
259+
toClass.isValue -> {
260+
val constructor =
261+
toClass.primaryConstructor ?: error("Value type $toClass doesn't have primary constructor")
262+
val underlyingType = constructor.parameters.single().type
263+
val converter = getConverter(from, underlyingType)
264+
?: throw TypeConverterNotFoundException(from, underlyingType, null)
265+
return convert<Any> {
266+
val converted = converter(it)
267+
if (converted == null && !underlyingType.isMarkedNullable) {
268+
throw TypeConversionException(it, from, underlyingType, null)
269+
}
270+
constructor.call(converted)
266271
}
267-
constructor.call(converted)
268272
}
269-
}
270273

271-
return when {
272274
fromClass == String::class -> {
273275
val parser = Parsers[to.withNullability(false)]
274276
when {

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema
4545
import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema
4646
import org.jetbrains.kotlinx.dataframe.size
4747
import kotlin.reflect.KType
48+
import kotlin.reflect.full.isSubtypeOf
4849
import kotlin.reflect.full.withNullability
4950
import kotlin.reflect.jvm.jvmErasure
51+
import kotlin.reflect.typeOf
5052

5153
private val logger = KotlinLogging.logger {}
5254

@@ -144,6 +146,25 @@ internal fun AnyFrame.convertToImpl(
144146
val from = originalColumn.type()
145147
val to = targetSchema.type
146148
val converter = dsl.getConverter(from, targetSchema)
149+
?: run {
150+
// Special case for Char columns:
151+
// If there is no explicit Char converter,
152+
// check if we have any converters for String -> target
153+
// if so, we can convert Char -> String -> target
154+
// this allows `parser {}` to work both for Strings and Chars :)
155+
156+
if (!from.isSubtypeOf(typeOf<Char?>())) return@run null
157+
158+
val stringConverter = dsl.getConverter(
159+
fromType = typeOf<String>().withNullability(from.isMarkedNullable),
160+
toSchema = targetSchema,
161+
) ?: return@run null
162+
163+
Converter(
164+
transform = { stringConverter.transform(this, (it as Char?)?.toString()) },
165+
skipNulls = stringConverter.skipNulls,
166+
)
167+
}
147168

148169
val convertedColumn = if (converter != null) {
149170
val nullsAllowed = to.isMarkedNullable

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,10 @@ internal fun <T> DataFrame<T>.parseImpl(options: ParserOptions?, columns: Column
730730
.asColumnGroup(col.name())
731731
.asDataColumn()
732732

733+
// Base case, parse the column if it's a `Char?` column
734+
col.isSubtypeOf<Char?>() ->
735+
col.map { it?.toString() }.tryParseImpl(options)
736+
733737
// Base case, parse the column if it's a `String?` column
734738
col.isSubtypeOf<String?>() ->
735739
col.cast<String?>().tryParseImpl(options)

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,38 @@ class ConvertToTests {
5151
df.convertTo<Schema> { parser { A(it.toInt()) } }
5252
.single()
5353
.a.value shouldBe 1
54+
55+
// shortcut for:
56+
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
57+
.single()
58+
.a.value shouldBe 1
59+
}
60+
61+
@Test
62+
fun `convert from char with parser`() {
63+
val df = dataFrameOf("a")('1')
64+
65+
shouldThrow<TypeConverterNotFoundException> {
66+
df.convertTo<Schema>()
67+
}
68+
69+
// Char -> String -> Target
70+
df.convertTo<Schema> { parser { A(it.toInt()) } }
71+
.single()
72+
.a.value shouldBe 1
73+
74+
// shortcut for:
75+
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
76+
.single()
77+
.a.value shouldBe 1
78+
79+
// Char -> Target
80+
df.convertTo<Schema> {
81+
parser<A> { error("should not be triggered if convert<Char>() is present") }
82+
convert<String>().with<_, A> { error("should not be triggered if convert<Char>() is present") }
83+
84+
convert<Char>().with { A(it.digitToInt()) }
85+
}.single().a.value shouldBe 1
5486
}
5587

5688
@Test
@@ -335,4 +367,31 @@ class ConvertToTests {
335367
DataFrame.emptyOf<Entry>(),
336368
)
337369
}
370+
371+
enum class SimpleEnum { A, B }
372+
373+
@DataSchema
374+
interface SchemaWithNullableEnum {
375+
val a: SimpleEnum?
376+
}
377+
378+
@Test
379+
fun `convert Char to Enum`() {
380+
val df = dataFrameOf("a")('A', 'B', null)
381+
382+
val converted = df.convertTo<SchemaWithNullableEnum>()
383+
converted["a"].type() shouldBe typeOf<SimpleEnum?>()
384+
converted shouldBe dataFrameOf("a")(SimpleEnum.A, SimpleEnum.B, null)
385+
}
386+
387+
@Test
388+
fun `convert Char to Enum custom charParser`() {
389+
val df = dataFrameOf("a")('a', 'b', null)
390+
391+
val converted = df.convertTo<SchemaWithNullableEnum> {
392+
parser { SimpleEnum.valueOf(it.uppercase()) }
393+
}
394+
converted["a"].type() shouldBe typeOf<SimpleEnum?>()
395+
converted shouldBe dataFrameOf("a")(SimpleEnum.A, SimpleEnum.B, null)
396+
}
338397
}

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/parse.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.jetbrains.kotlinx.dataframe.api
22

3+
import io.kotest.assertions.throwables.shouldThrow
34
import io.kotest.matchers.should
45
import io.kotest.matchers.shouldBe
56
import io.kotest.matchers.shouldNotBe
@@ -42,9 +43,10 @@ class ParseTests {
4243
@Test
4344
fun `parse to chars`() {
4445
val char = columnOf('a', 'b', 'c')
45-
char.parse() shouldBe char
46+
shouldThrow<IllegalStateException> { char.parse() }
4647
char.tryParse() shouldBe char
4748
char.convertToString().parse() shouldBe char
49+
char.convertToString().tryParse() shouldBe char
4850
}
4951

5052
@Test

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/Modify.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package org.jetbrains.kotlinx.dataframe.samples.api
44

55
import io.kotest.matchers.shouldBe
6+
import org.jetbrains.kotlinx.dataframe.AnyFrame
67
import org.jetbrains.kotlinx.dataframe.DataFrame
78
import org.jetbrains.kotlinx.dataframe.DataRow
89
import org.jetbrains.kotlinx.dataframe.alsoDebug
@@ -1100,11 +1101,17 @@ class Modify : TestBase() {
11001101
@TransformDataFrameExpressions
11011102
fun customConverters() {
11021103
// SampleStart
1103-
val df = dataFrameOf("a", "b")(1, "2")
1104+
val df: AnyFrame = dataFrameOf(
1105+
"a" to columnOf(1, 2, 3),
1106+
"b" to columnOf("1", "2", "3"),
1107+
)
11041108
df.convertTo<MySchema> {
1105-
convert<Int>().with { MyType(it) } // converts `a` from Int to MyType
1106-
parser { MyType(it.toInt()) } // converts `b` from String to MyType
1107-
fill { c }.with { a.value + b.value } // computes missing column `c`
1109+
// providing the converter: Int -> MyType, so column `a` can be converted
1110+
convert<Int>().with { MyType(it) }
1111+
// providing the parser: String -> MyType, so column `b` can be converted
1112+
parser { MyType(it.toInt()) }
1113+
// providing the filler for `c`, as it's missing in `df`
1114+
fill { c }.with { a.value + b.value }
11081115
}
11091116
// SampleEnd
11101117
}

0 commit comments

Comments
 (0)