commit 4f63a229e1d27479581c1b54b15b3a1d29d45e8c Author: Andrew Golovashevich Date: Sat Nov 9 21:58:16 2024 +0300 Safe sign conversions for int types diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7af7fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ +/.idea/ +/.gradle/ +/gradle/ +/gradlew* +/.kotlin/ +/kotlin-js-store/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..c6cdfae --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,40 @@ +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.defineAllMultiplatformTargets +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.optInContracts +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.defineXomrkGiteaMavenRepo +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk +import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.warningsAsErrors + +buildscript { + repositories { + mavenCentral() + maven("https://maven.landgrafhomyak.ru/") + } + + dependencies { + classpath("ru.landgrafhomyak.kotlin:kotlin-mpp-gradle-build:v0.2k2.0.20") + } +} + +group = "ru.landgrafhomyak.kotlin" +version = "v1.0" + +repositories { + mavenCentral() +} + +xomrk { + kotlin { + explicitApi() + warningsAsErrors() + optInContracts() + + jvmToolchain(8) + defineAllMultiplatformTargets() + } + + publishing { + repositories { + defineXomrkGiteaMavenRepo() + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..694bc06 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +kotlin.code.style=official +kotlin.native.ignoreDisabledTargets=true \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..57b7000 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,2 @@ +rootProject.name = "kotlin-utilities" + diff --git a/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_sign.kt b/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_sign.kt new file mode 100644 index 0000000..f80c4cb --- /dev/null +++ b/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_sign.kt @@ -0,0 +1,321 @@ +@file:JvmName("IntConversions_Sign") +@file:Suppress("NOTHING_TO_INLINE") + +package ru.landrafhomyak.kotlin.multiplatform_switches + +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + + +/********************************************* Reinterpret sign casts ****************************************/ + + +/** + * Converts this [Byte] value to [UByte]. + * + * If this value is positive, the resulting `UByte` value represents the same numerical value as this `Byte`. + * + * The resulting `UByte` value has the same binary representation as this `Byte` value. + */ +public inline fun Byte.asUByte(): UByte = this.toUByte() + + +/** + * Converts this [UByte] value to [Byte]. + * + * If this value is less than or equals to [Byte.MAX_VALUE], the resulting `Byte` value represents + * the same numerical value as this `UByte`. Otherwise the result is negative. + * + * The resulting `Byte` value has the same binary representation as this `UByte` value. + */ +public inline fun UByte.asByte(): Byte = this.toByte() + +/** + * Converts this [Short] value to [UShort]. + * + * If this value is positive, the resulting `UShort` value represents the same numerical value as this `Short`. + * + * The resulting `UShort` value has the same binary representation as this `Short` value. + */ +public inline fun Short.asUShort(): UShort = this.toUShort() + + +/** + * Converts this [UShort] value to [Short]. + * + * If this value is less than or equals to [Short.MAX_VALUE], the resulting `Short` value represents + * the same numerical value as this `UShort`. Otherwise the result is negative. + * + * The resulting `Short` value has the same binary representation as this `UShort` value. + */ +public inline fun UShort.asShort(): Short = this.toShort() + +/** + * Converts this [Int] value to [UInt]. + * + * If this value is positive, the resulting `UInt` value represents the same numerical value as this `Int`. + * + * The resulting `UInt` value has the same binary representation as this `Int` value. + */ +public inline fun Int.asUInt(): UInt = this.toUInt() + +/** + * Converts this [UInt] value to [Int]. + * + * If this value is less than or equals to [Int.MAX_VALUE], the resulting `Int` value represents + * the same numerical value as this `UInt`. Otherwise the result is negative. + * + * The resulting `Int` value has the same binary representation as this `UInt` value. + */ +public inline fun UInt.asInt(): Int = this.toInt() + + +/** + * Converts this [Long] value to [ULong]. + * + * If this value is positive, the resulting `ULong` value represents the same numerical value as this `Long`. + * + * The resulting `ULong` value has the same binary representation as this `Long` value. + */ +public inline fun Long.asULong(): ULong = this.toULong() + +/** + * Converts this [ULong] value to [Long]. + * + * If this value is less than or equals to [Long.MAX_VALUE], the resulting `Long` value represents + * the same numerical value as this `ULong`. Otherwise the result is negative. + * + * The resulting `Long` value has the same binary representation as this `ULong` value. + */ +public inline fun ULong.asLong(): Long = this.toLong() + + +/********************************************* Safe sign casts (or null) ****************************************/ + + +/** + * Converts this [Byte] value to [UByte]. + * + * If this value is positive, the resulting [UByte] value represents the same numerical value as this [Byte]. + * Otherwise `null` returned. + */ + +public inline fun Byte.toUByteOrNull(): UByte? = if (this < 0) null else this.asUByte() + + +/** + * Converts this [UByte] value to [Byte]. + * + * If this value is less than or equals to [Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [UByte]. + * Otherwise `null` returned. + */ +public inline fun UByte.toByteOrNull(): Byte? = if (this >= Byte.MAX_VALUE.asUByte()) null else this.asByte() + +/** + * Converts this [Short] value to [UShort]. + * + * If this value is positive, the resulting [UShort] value represents the same numerical value as this [Short]. + * Otherwise `null` returned. + */ +public inline fun Short.toUShortOrNull(): UShort? = if (this < 0) null else this.asUShort() + + +/** + * Converts this [UShort] value to [Short]. + * + * If this value is less than or equals to [Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [UShort]. + * Otherwise `null` returned. + */ +public inline fun UShort.toShortOrNull(): Short? = if (this >= Short.MAX_VALUE.asUShort()) null else this.asShort() + +/** + * Converts this [Int] value to [UInt]. + * + * If this value is positive, the resulting [UInt] value represents the same numerical value as this [Int]. + * Otherwise `null` returned. + */ +public inline fun Int.toUIntOrNull(): UInt? = if (this < 0) null else this.asUInt() + +/** + * Converts this [UInt] value to [Int]. + * + * If this value is less than or equals to [Int.MAX_VALUE], the resulting [Int] value represents + * the same numerical value as this [UInt]. + * Otherwise `null` returned. + */ +public inline fun UInt.toIntOrNull(): Int? = if (this >= Int.MAX_VALUE.asUInt()) null else this.asInt() + + +/** + * Converts this [Long] value to [ULong]. + * + * If this value is positive, the resulting [ULong] value represents the same numerical value as this [Long]. + * Otherwise `null` returned. + */ +public inline fun Long.toULongOrNull(): ULong? = if (this < 0) null else this.asULong() + +/** + * Converts this [ULong] value to [Long]. + * + * If this value is less than or equals to [Long.MAX_VALUE], the resulting [Long] value represents + * the same numerical value as this [ULong]. + * Otherwise `null` returned. + */ +public inline fun ULong.toLongOrNull(): Long? = if (this >= Long.MAX_VALUE.asULong()) null else this.asLong() + + +/********************************************* Conversion error formatters ****************************************/ + +@Suppress("FunctionName") +private inline fun _formatUnsignedToSignedOverflow(value: U, limit: S) = + "Can't convert ${U::class.simpleName!!} value to ${S::class.simpleName!!} because $value > $limit" + +@Suppress("FunctionName") +private inline fun _formatSignedToUnsignedNegative(value: S) = + "Can't convert ${S::class.simpleName!!} value to ${U::class.simpleName!!} because $value is negative" + + +public fun Byte.formatToUByteError(): String = _formatSignedToUnsignedNegative(this) + +public fun UByte.formatToByteError(): String = _formatUnsignedToSignedOverflow(this, Byte.MAX_VALUE) + +public fun Short.formatToUShortError(): String = _formatSignedToUnsignedNegative(this) + +public fun UShort.formatToShortError(): String = _formatUnsignedToSignedOverflow(this, Short.MAX_VALUE) + +public fun Int.formatToUIntError(): String = _formatSignedToUnsignedNegative(this) + +public fun UInt.formatToIntError(): String = _formatUnsignedToSignedOverflow(this, Int.MAX_VALUE) + +public fun Long.formatToULongError(): String = _formatSignedToUnsignedNegative(this) + +public fun ULong.formatToLongError(): String = _formatUnsignedToSignedOverflow(this, Long.MAX_VALUE) + + +/********************************************* Safe sign casts (or exception) ****************************************/ + +/** + * Converts this [Byte] value to [UByte]. + * + * If this value is positive, the resulting [UByte] value represents the same numerical value as this [Byte]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ + +public inline fun Byte.toUByteOrThrow(exception: (Byte) -> Nothing = { b -> throw IllegalArgumentException(b.formatToUByteError()) }): UByte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toUByteOrNull() ?: exception(this) +} + + +/** + * Converts this [UByte] value to [Byte]. + * + * If this value is less than or equals to [Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [UByte]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UByte.toByteOrThrow(exception: (UByte) -> Nothing = { b -> throw IllegalArgumentException(b.formatToByteError()) }): Byte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toByteOrNull() ?: exception(this) +} + +/** + * Converts this [Short] value to [UShort]. + * + * If this value is positive, the resulting [UShort] value represents the same numerical value as this [Short]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Short.toUShortOrThrow(exception: (Short) -> Nothing = { b -> throw IllegalArgumentException(b.formatToUShortError()) }): UShort { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toUShortOrNull() ?: exception(this) +} + + +/** + * Converts this [UShort] value to [Short]. + * + * If this value is less than or equals to [Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [UShort]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UShort.toShortOrThrow(exception: (UShort) -> Nothing = { b -> throw IllegalArgumentException(b.formatToShortError()) }): Short { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toShortOrNull() ?: exception(this) +} + +/** + * Converts this [Int] value to [UInt]. + * + * If this value is positive, the resulting [UInt] value represents the same numerical value as this [Int]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Int.toUIntOrThrow(exception: (Int) -> Nothing = { b -> throw IllegalArgumentException(b.formatToUIntError()) }): UInt { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toUIntOrNull() ?: exception(this) +} + +/** + * Converts this [UInt] value to [Int]. + * + * If this value is less than or equals to [Int.MAX_VALUE], the resulting [Int] value represents + * the same numerical value as this [UInt]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UInt.toIntOrThrow(exception: (UInt) -> Nothing = { b -> throw IllegalArgumentException(b.formatToIntError()) }): Int { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toIntOrNull() ?: exception(this) +} + + +/** + * Converts this [Long] value to [ULong]. + * + * If this value is positive, the resulting [ULong] value represents the same numerical value as this [Long]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Long.toULongOrThrow(exception: (Long) -> Nothing = { b -> throw IllegalArgumentException(b.formatToULongError()) }): ULong { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toULongOrNull() ?: exception(this) +} + + +/** + * Converts this [ULong] value to [Long]. + * + * If this value is less than or equals to [Long.MAX_VALUE], the resulting [Long] value represents + * the same numerical value as this [ULong]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun ULong.toLongOrThrow(exception: (ULong) -> Nothing = { b -> throw IllegalArgumentException(b.formatToLongError()) }): Long { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.toLongOrNull() ?: exception(this) +} + +