Safe sign conversions for int types

This commit is contained in:
Andrew Golovashevich 2024-11-09 21:58:16 +03:00
commit 4f63a229e1
5 changed files with 372 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build/
/.idea/
/.gradle/
/gradle/
/gradlew*
/.kotlin/
/kotlin-js-store/

40
build.gradle.kts Normal file
View File

@ -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()
}
}
}

2
gradle.properties Normal file
View File

@ -0,0 +1,2 @@
kotlin.code.style=official
kotlin.native.ignoreDisabledTargets=true

2
settings.gradle.kts Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = "kotlin-utilities"

View File

@ -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 <reified S, reified U> _formatUnsignedToSignedOverflow(value: U, limit: S) =
"Can't convert ${U::class.simpleName!!} value to ${S::class.simpleName!!} because $value > $limit"
@Suppress("FunctionName")
private inline fun <reified S, reified U> _formatSignedToUnsignedNegative(value: S) =
"Can't convert ${S::class.simpleName!!} value to ${U::class.simpleName!!} because $value is negative"
public fun Byte.formatToUByteError(): String = _formatSignedToUnsignedNegative<Byte, UByte>(this)
public fun UByte.formatToByteError(): String = _formatUnsignedToSignedOverflow<Byte, UByte>(this, Byte.MAX_VALUE)
public fun Short.formatToUShortError(): String = _formatSignedToUnsignedNegative<Short, UShort>(this)
public fun UShort.formatToShortError(): String = _formatUnsignedToSignedOverflow<Short, UShort>(this, Short.MAX_VALUE)
public fun Int.formatToUIntError(): String = _formatSignedToUnsignedNegative<Int, UInt>(this)
public fun UInt.formatToIntError(): String = _formatUnsignedToSignedOverflow<Int, UInt>(this, Int.MAX_VALUE)
public fun Long.formatToULongError(): String = _formatSignedToUnsignedNegative<Long, ULong>(this)
public fun ULong.formatToLongError(): String = _formatUnsignedToSignedOverflow<Long, ULong>(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)
}