diff --git a/build.gradle.kts b/build.gradle.kts index 2ab279f51e..b64548af47 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -194,6 +194,7 @@ allprojects { // enables support for kotlin.time.Instant as kotlinx.datetime.Instant was deprecated; Issue #1350 // Can be removed once kotlin.time.Instant is marked "stable". optIn.add("kotlin.time.ExperimentalTime") + freeCompilerArgs.addAll("-Xallow-contracts-on-more-functions") } } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt index 21dc50ec8d..1d7293550d 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/DataColumnType.kt @@ -1,8 +1,9 @@ -@file:OptIn(ExperimentalContracts::class) +@file:OptIn(ExperimentalContracts::class, ExperimentalExtendedContracts::class) package org.jetbrains.kotlinx.dataframe.api import org.jetbrains.kotlinx.dataframe.AnyCol +import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.columns.ColumnKind import org.jetbrains.kotlinx.dataframe.columns.FrameColumn @@ -17,6 +18,7 @@ import org.jetbrains.kotlinx.dataframe.util.IS_COMPARABLE import org.jetbrains.kotlinx.dataframe.util.IS_COMPARABLE_REPLACE import org.jetbrains.kotlinx.dataframe.util.IS_INTER_COMPARABLE_IMPORT import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.ExperimentalExtendedContracts import kotlin.contracts.contract import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf @@ -40,21 +42,36 @@ public fun AnyCol.isValueColumn(): Boolean { public fun AnyCol.isSubtypeOf(type: KType): Boolean = this.type.isSubtypeOf(type) && (!this.type.isMarkedNullable || type.isMarkedNullable) -public inline fun AnyCol.isSubtypeOf(): Boolean = isSubtypeOf(typeOf()) +public inline fun AnyCol.isSubtypeOf(): Boolean { + contract { returns(true) implies (this@isSubtypeOf is DataColumn) } + return isSubtypeOf(typeOf()) +} -public inline fun AnyCol.isType(): Boolean = type() == typeOf() +public inline fun AnyCol.isType(): Boolean { + contract { returns(true) implies (this@isType is DataColumn) } + return type() == typeOf() +} /** Returns `true` when this column's type is a subtype of `Number?` */ -public fun AnyCol.isNumber(): Boolean = isSubtypeOf() +public fun AnyCol.isNumber(): Boolean { + contract { returns(true) implies (this@isNumber is ValueColumn) } + return isSubtypeOf() +} /** Returns `true` only when this column's type is exactly `Number` or `Number?`. */ -public fun AnyCol.isMixedNumber(): Boolean = type().isMixedNumber() +public fun AnyCol.isMixedNumber(): Boolean { + contract { returns(true) implies (this@isMixedNumber is ValueColumn) } + return type().isMixedNumber() +} /** * Returns `true` when this column has the (nullable) type of either: * [Byte], [Short], [Int], [Long], [Float], or [Double]. */ -public fun AnyCol.isPrimitiveNumber(): Boolean = type().isPrimitiveNumber() +public fun AnyCol.isPrimitiveNumber(): Boolean { + contract { returns(true) implies (this@isPrimitiveNumber is ValueColumn) } + return type().isPrimitiveNumber() +} /** * Returns `true` when this column has the (nullable) type of either: @@ -63,9 +80,15 @@ public fun AnyCol.isPrimitiveNumber(): Boolean = type().isPrimitiveNumber() * Careful: Will return `true` if the column contains multiple number types that * might NOT be primitive. */ -public fun AnyCol.isPrimitiveOrMixedNumber(): Boolean = type().isPrimitiveOrMixedNumber() +public fun AnyCol.isPrimitiveOrMixedNumber(): Boolean { + contract { returns(true) implies (this@isPrimitiveOrMixedNumber is ValueColumn) } + return type().isPrimitiveOrMixedNumber() +} -public fun AnyCol.isList(): Boolean = typeClass == List::class +public fun AnyCol.isList(): Boolean { + contract { returns(true) implies (this@isList is ValueColumn>) } + return typeClass == List::class +} /** @include [valuesAreComparable] */ @Deprecated( @@ -84,4 +107,7 @@ public fun AnyCol.isComparable(): Boolean = valuesAreComparable() * * Technically, this means the values' common type `T(?)` is a subtype of [Comparable]`(?)` */ -public fun AnyCol.valuesAreComparable(): Boolean = isValueColumn() && type().isIntraComparable() +public fun DataColumn.valuesAreComparable(): Boolean { + contract { returns(true) implies (this@valuesAreComparable is ValueColumn>) } + return isValueColumn() && type().isIntraComparable() +} diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/typeConversions.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/typeConversions.kt index 682423fc77..da0db7d08f 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/typeConversions.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/typeConversions.kt @@ -89,13 +89,13 @@ public fun DataColumn.asNumbers(): ValueColumn { public fun DataColumn.asComparable(): DataColumn> { require(valuesAreComparable()) - return this as DataColumn> + return this } @JvmName("asComparableNullable") public fun DataColumn.asComparable(): DataColumn?> { require(valuesAreComparable()) - return this as DataColumn?> + return this } public fun ColumnReference.castToNotNullable(): ColumnReference = cast() diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/describe.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/describe.kt index 9611289994..4ebd825cea 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/describe.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/describe.kt @@ -55,8 +55,8 @@ internal fun describeImpl(cols: List): DataFrame { ?.key } if (hasNumericCols) { - ColumnDescription::mean from { if (it.isNumber()) it.asNumbers().mean() else null } - ColumnDescription::std from { if (it.isNumber()) it.asNumbers().std() else null } + ColumnDescription::mean from { if (it.isNumber()) it.mean() else null } + ColumnDescription::std from { if (it.isNumber()) it.std() else null } } if (hasComparableCols || hasNumericCols) { ColumnDescription::min from inferType { @@ -113,10 +113,10 @@ private fun List.collectAll(atAnyDepth: Boolean): List = @Suppress("UNCHECKED_CAST") private fun DataColumn.convertToComparableOrNull(): DataColumn?>? { return when { - valuesAreComparable() -> asComparable() + valuesAreComparable() -> this // Found incomparable number types, convert all to Double first - isNumber() -> cast().map { + isNumber() -> map { if (it?.isPrimitiveNumber() == false) { // Cannot calculate statistics of a non-primitive number type return@convertToComparableOrNull null diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt index 2a74a7f84f..c2fe3548a5 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/parse.kt @@ -736,7 +736,7 @@ internal fun DataFrame.parseImpl(options: ParserOptions?, columns: Column // Base case, parse the column if it's a `String?` column col.isSubtypeOf() -> - col.cast().tryParseImpl(options) + col.tryParseImpl(options) else -> col } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt index dec55ef272..6dc0794489 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt @@ -170,7 +170,7 @@ internal fun AnyFrame.toHtmlData( configuration: DisplayConfiguration, ): ColumnDataForJs { val values = if (rowsLimit != null) rows().take(rowsLimit) else rows() - val scale = if (col.isNumber()) col.asNumbers().scale() else 1 + val scale = if (col.isNumber()) col.scale() else 1 val format = if (scale > 0) { RendererDecimalFormat.fromPrecision(scale) } else { diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/string.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/string.kt index 237bb1c74d..6a1c7d9cdf 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/string.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/string.kt @@ -48,7 +48,7 @@ public fun AnyFrame.renderToString( } val values = cols.map { val top = it.take(rowsLimit) - val precision = if (top.isNumber()) top.asNumbers().scale() else 0 + val precision = if (top.isNumber()) top.scale() else 0 val decimalFormat = if (precision >= 0) RendererDecimalFormat.fromPrecision(precision) else RendererDecimalFormat.of("%e") top.values().map { diff --git a/dataframe-json/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt b/dataframe-json/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt index ee2024add4..7abfb9cccb 100644 --- a/dataframe-json/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt +++ b/dataframe-json/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/io/writeJson.kt @@ -176,7 +176,7 @@ internal fun encodeValue(col: AnyCol, index: Int, customEncoders: List matchingEncoder.encode(col[index]) col.isList() -> col[index]?.let { list -> - val values = (list as List<*>).map { convert(it) } + val values = list.map { convert(it) } JsonArray(values) } ?: JsonArray(emptyList())