From af6396fbcb1f852254ed7c828f51202fe8fde750 Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Sun, 10 Nov 2024 15:30:52 +0300 Subject: [PATCH] Safe size conversions for int types --- .../int_conversions_size.kt | 438 ++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_size.kt diff --git a/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_size.kt b/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_size.kt new file mode 100644 index 0000000..371c4fd --- /dev/null +++ b/src/commonMain/kotlin/ru/landrafhomyak/kotlin/multiplatform_switches/int_conversions_size.kt @@ -0,0 +1,438 @@ +@file:JvmName("IntConversions_Size") +@file:Suppress("NOTHING_TO_INLINE") + +package ru.landrafhomyak.kotlin.multiplatform_switches + +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + + +/********************************** extend *****************************************/ + +/** + * Converts this [Byte] value to [Short]. + * + * The resulting [Short] value represents the same numerical value as this [Byte]. + */ +public inline fun Byte.asShort(): Short = this.toShort() + +/** + * Converts this [Byte] value to [Int]. + * + * The resulting [Int] value represents the same numerical value as this [Byte]. + */ +public inline fun Byte.asInt(): Int = this.toInt() + +/** + * Converts this [Byte] value to [Long]. + * + * The resulting [Long] value represents the same numerical value as this [Byte]. + */ +public inline fun Byte.asLong(): Long = this.toLong() + +/** + * Converts this [UByte] value to [UShort]. + * + * The resulting [UShort] value represents the same numerical value as this [UByte]. + */ +public inline fun UByte.asUShort(): UShort = this.toUShort() + +/** + * Converts this [UByte] value to [UInt]. + * + * The resulting [UInt] value represents the same numerical value as this [UByte]. + */ +public inline fun UByte.asUInt(): UInt = this.toUInt() + +/** + * Converts this [UByte] value to [ULong]. + * + * The resulting [ULong] value represents the same numerical value as this [UByte]. + */ +public inline fun UByte.asULong(): ULong = this.toULong() + +/** + * Converts this [Short] value to [Int]. + * + * The resulting [Int] value represents the same numerical value as this [Short]. + */ +public inline fun Short.asInt(): Int = this.toInt() + +/** + * Converts this [Short] value to [Long]. + * + * The resulting [Long] value represents the same numerical value as this [Short]. + */ +public inline fun Short.asLong(): Long = this.toLong() + +/** + * Converts this [UShort] value to [UInt]. + * + * The resulting [UInt] value represents the same numerical value as this [UShort]. + */ +public inline fun UShort.asUInt(): UInt = this.toUInt() + +/** + * Converts this [UShort] value to [ULong]. + * + * The resulting [ULong] value represents the same numerical value as this [UShort]. + */ +public inline fun UShort.asULong(): ULong = this.toULong() + +/** + * Converts this [Int] value to [Long]. + * + * The resulting [Long] value represents the same numerical value as this [Int]. + */ +public inline fun Int.asLong(): Long = this.toLong() + +/** + * Converts this [UInt] value to [ULong]. + * + * The resulting [ULong] value represents the same numerical value as this [UInt]. + */ +public inline fun UInt.asULong(): ULong = this.toULong() + + +/******************************** trims (or null) ****************************/ + + +/** + * Converts this [Short] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Short]. + * Otherwise `null` returned. + */ +public inline fun Short.asByteOrNull(): Byte? = if (this in (Byte.MIN_VALUE.asShort())..(Byte.MAX_VALUE.asShort())) this.toByte() else null + + +/** + * Converts this [UShort] value to [UByte]. + * + * If this value is in 0..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [UShort]. + * Otherwise `null` returned. + */ +public inline fun UShort.asUByteOrNull(): UByte? = if (this <= UByte.MAX_VALUE.asUShort()) this.toUByte() else null + + +/** + * Converts this [Int] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Int]. + * Otherwise `null` returned. + */ +public inline fun Int.asByteOrNull(): Byte? = if (this in (Byte.MIN_VALUE.asInt())..(Byte.MAX_VALUE.asInt())) this.toByte() else null + +/** + * Converts this [Int] value to [Short]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [Int]. + * Otherwise `null` returned. + */ +public inline fun Int.asShortOrNull(): Short? = if (this in (Short.MIN_VALUE.asInt())..(Short.MAX_VALUE.asInt())) this.toShort() else null + + +/** + * Converts this [UInt] value to [UByte]. + * + * If this value is in [UByte.MIN_VALUE]..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [UInt]. + * Otherwise `null` returned. + */ +public inline fun UInt.asUByteOrNull(): UByte? = if (this <= UByte.MAX_VALUE.asUInt()) this.toUByte() else null + +/** + * Converts this [Int] value to [Short]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [Int]. + * Otherwise `null` returned. + */ +public inline fun UInt.asUShortOrNull(): UShort? = if (this <= UShort.MAX_VALUE.asUInt()) this.toUShort() else null + + +/** + * Converts this [Long] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Long]. + * Otherwise `null` returned. + */ +public inline fun Long.asByteOrNull(): Byte? = if (this in (Byte.MIN_VALUE.asLong())..(Byte.MAX_VALUE.asLong())) this.toByte() else null + +/** + * Converts this [Long] value to [Short]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [Long]. + * Otherwise `null` returned. + */ +public inline fun Long.asShortOrNull(): Short? = if (this in (Short.MIN_VALUE.asLong())..(Short.MAX_VALUE.asLong())) this.toShort() else null + + +/** + * Converts this [Long] value to [Int]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Int] value represents + * the same numerical value as this [Long]. + * Otherwise `null` returned. + */ +public inline fun Long.asIntOrNull(): Int? = if (this in (Int.MIN_VALUE.asLong())..(Int.MAX_VALUE.asLong())) this.toInt() else null + + +/** + * Converts this [ULong] value to [UByte]. + * + * If this value is in [UByte.MIN_VALUE]..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [ULong]. + * Otherwise `null` returned. + */ +public inline fun ULong.asUByteOrNull(): UByte? = if (this in (UByte.MIN_VALUE.asULong())..(UByte.MAX_VALUE.asULong())) this.toUByte() else null + +/** + * Converts this [ULong] value to [UShort]. + * + * If this value is in [UShort.MIN_VALUE]..[UShort.MAX_VALUE], the resulting [UShort] value represents + * the same numerical value as this [ULong]. + * Otherwise `null` returned. + */ +public inline fun ULong.asUShortOrNull(): UShort? = if (this in (UShort.MIN_VALUE.asULong())..(UShort.MAX_VALUE.asULong())) this.toUShort() else null + + +/** + * Converts this [ULong] value to [UInt]. + * + * If this value is in [UShort.MIN_VALUE]..[UShort.MAX_VALUE], the resulting [UInt] value represents + * the same numerical value as this [ULong]. + * Otherwise `null` returned. + */ +public inline fun ULong.asUIntOrNull(): UInt? = if (this in (UInt.MIN_VALUE.asULong())..(UInt.MAX_VALUE.asULong())) this.toUInt() else null + + +/********************************************* Conversion error formatters ****************************************/ + +@Suppress("FunctionName") +private inline fun _formatTrimOverflowS(value: W, min: N, max: N) = + "Can't convert ${W::class.simpleName!!} value to ${N::class.simpleName!!} because $value !in ${min}..${max}" + +@Suppress("FunctionName") +private inline fun _formatTrimOverflowU(value: W, max: N) = + "Can't convert ${W::class.simpleName!!} value to ${N::class.simpleName!!} because $value > $max" + + +public fun Short.formatAsByteError(): String = _formatTrimOverflowS(this, Byte.MIN_VALUE, Byte.MAX_VALUE) + +public fun UShort.formatAsUByteError(): String = _formatTrimOverflowU(this, UByte.MAX_VALUE) + +public fun Int.formatAsByteError(): String = _formatTrimOverflowS(this, Byte.MIN_VALUE, Byte.MAX_VALUE) + +public fun Int.formatAsShortError(): String = _formatTrimOverflowS(this, Short.MIN_VALUE, Short.MAX_VALUE) + +public fun UInt.formatAsUByteError(): String = _formatTrimOverflowU(this, UByte.MAX_VALUE) + +public fun UInt.formatAsUShortError(): String = _formatTrimOverflowU(this, UShort.MAX_VALUE) + +public fun Long.formatAsByteError(): String = _formatTrimOverflowS(this, Byte.MIN_VALUE, Byte.MAX_VALUE) + +public fun Long.formatAsShortError(): String = _formatTrimOverflowS(this, Short.MIN_VALUE, Short.MAX_VALUE) + +public fun Long.formatAsIntError(): String = _formatTrimOverflowS(this, Int.MIN_VALUE, Int.MAX_VALUE) + +public fun ULong.formatAsUByteError(): String = _formatTrimOverflowU(this, UByte.MAX_VALUE) + +public fun ULong.formatAsUShortError(): String = _formatTrimOverflowU(this, UShort.MAX_VALUE) + +public fun ULong.formatAsUIntError(): String = _formatTrimOverflowU(this, UInt.MAX_VALUE) + +/******************************** trims (or exception) ****************************/ + + +/** + * Converts this [Short] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Short]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Short.asByteOrThrow(exception: (Short) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsByteError()) }): Byte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asByteOrNull() ?: exception(this) +} + +/** + * Converts this [UShort] value to [UByte]. + * + * If this value is in 0..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [UShort]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UShort.asByteOrThrow(exception: (UShort) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUByteError()) }): UByte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUByteOrNull() ?: exception(this) +} + +/** + * Converts this [Int] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Int]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Int.asByteOrThrow(exception: (Int) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsByteError()) }): Byte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asByteOrNull() ?: exception(this) +} + +/** + * Converts this [Int] value to [Short]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [Int]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Int.asShortOrThrow(exception: (Int) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsShortError()) }): Short { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asShortOrNull() ?: exception(this) +} + +/** + * Converts this [UInt] value to [UByte]. + * + * If this value is in 0..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [UInt]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UInt.asUByteOrThrow(exception: (UInt) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUByteError()) }): UByte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUByteOrNull() ?: exception(this) +} + + +/** + * Converts this [UInt] value to [UShort]. + * + * If this value is in 0..[UShort.MAX_VALUE], the resulting [UShort] value represents + * the same numerical value as this [UShort]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun UInt.asUShortOrThrow(exception: (UInt) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUShortError()) }): UShort { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUShortOrNull() ?: exception(this) +} + + +/** + * Converts this [Long] value to [Byte]. + * + * If this value is in [Byte.MIN_VALUE]..[Byte.MAX_VALUE], the resulting [Byte] value represents + * the same numerical value as this [Long]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Long.asByteOrThrow(exception: (Long) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsByteError()) }): Byte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asByteOrNull() ?: exception(this) +} + +/** + * Converts this [Long] value to [Short]. + * + * If this value is in [Short.MIN_VALUE]..[Short.MAX_VALUE], the resulting [Short] value represents + * the same numerical value as this [Long]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Long.asShortOrThrow(exception: (Long) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsShortError()) }): Short { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asShortOrNull() ?: exception(this) +} + +/** + * Converts this [Long] value to [Int]. + * + * If this value is in [Int.MIN_VALUE]..[Int.MAX_VALUE], the resulting [Int] value represents + * the same numerical value as this [Long]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun Long.asIntOrThrow(exception: (Long) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsIntError()) }): Int { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asIntOrNull() ?: exception(this) +} + +/** + * Converts this [ULong] value to [UByte]. + * + * If this value is in 0..[UByte.MAX_VALUE], the resulting [UByte] value represents + * the same numerical value as this [ULong]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun ULong.asUByteOrThrow(exception: (ULong) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUByteError()) }): UByte { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUByteOrNull() ?: exception(this) +} + + +/** + * Converts this [ULong] value to [UShort]. + * + * If this value is in 0..[UShort.MAX_VALUE], the resulting [UShort] value represents + * the same numerical value as this [ULong]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun ULong.asUShortOrThrow(exception: (ULong) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUShortError()) }): UShort { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUShortOrNull() ?: exception(this) +} + + +/** + * Converts this [ULong] value to [UInt]. + * + * If this value is in 0..[UInt.MAX_VALUE], the resulting [UInt] value represents + * the same numerical value as this [ULong]. + * Otherwise [exception] function called (by default throws [IllegalArgumentException]). + */ +public inline fun ULong.asUIntOrThrow(exception: (ULong) -> Nothing = { b -> throw IllegalArgumentException(b.formatAsUIntError()) }): UInt { + contract { + callsInPlace(exception, InvocationKind.AT_MOST_ONCE) + } + + return this.asUIntOrNull() ?: exception(this) +} +