From 35c6f09886b5ca4876eda00c2a88773d81ba3c77 Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Tue, 12 Mar 2019 21:37:32 +0300 Subject: [PATCH] Implement coerce extension functions for unsigned types --- .../stdlib/common/src/generated/_URanges.kt | 210 ++++++++++++++++++ libraries/stdlib/samples/build.gradle | 7 +- .../test/samples/comparisons/comparableOps.kt | 22 ++ .../kotlin-stdlib-runtime-merged.txt | 14 ++ .../src/templates/Comparables.kt | 78 ++++--- 5 files changed, 297 insertions(+), 34 deletions(-) diff --git a/libraries/stdlib/common/src/generated/_URanges.kt b/libraries/stdlib/common/src/generated/_URanges.kt index dd975939bbf..397ab38eb9e 100644 --- a/libraries/stdlib/common/src/generated/_URanges.kt +++ b/libraries/stdlib/common/src/generated/_URanges.kt @@ -281,3 +281,213 @@ public infix fun UShort.until(to: UShort): UIntRange { return this.toUInt() .. (to - 1u).toUInt() } +/** + * Ensures that this value is not less than the specified [minimumValue]. + * + * @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtLeastUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UInt.coerceAtLeast(minimumValue: UInt): UInt { + return if (this < minimumValue) minimumValue else this +} + +/** + * Ensures that this value is not less than the specified [minimumValue]. + * + * @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtLeastUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun ULong.coerceAtLeast(minimumValue: ULong): ULong { + return if (this < minimumValue) minimumValue else this +} + +/** + * Ensures that this value is not less than the specified [minimumValue]. + * + * @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtLeastUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UByte.coerceAtLeast(minimumValue: UByte): UByte { + return if (this < minimumValue) minimumValue else this +} + +/** + * Ensures that this value is not less than the specified [minimumValue]. + * + * @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtLeastUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UShort.coerceAtLeast(minimumValue: UShort): UShort { + return if (this < minimumValue) minimumValue else this +} + +/** + * Ensures that this value is not greater than the specified [maximumValue]. + * + * @return this value if it's less than or equal to the [maximumValue] or the [maximumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtMostUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UInt.coerceAtMost(maximumValue: UInt): UInt { + return if (this > maximumValue) maximumValue else this +} + +/** + * Ensures that this value is not greater than the specified [maximumValue]. + * + * @return this value if it's less than or equal to the [maximumValue] or the [maximumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtMostUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun ULong.coerceAtMost(maximumValue: ULong): ULong { + return if (this > maximumValue) maximumValue else this +} + +/** + * Ensures that this value is not greater than the specified [maximumValue]. + * + * @return this value if it's less than or equal to the [maximumValue] or the [maximumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtMostUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UByte.coerceAtMost(maximumValue: UByte): UByte { + return if (this > maximumValue) maximumValue else this +} + +/** + * Ensures that this value is not greater than the specified [maximumValue]. + * + * @return this value if it's less than or equal to the [maximumValue] or the [maximumValue] otherwise. + * + * @sample samples.comparisons.ComparableOps.coerceAtMostUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UShort.coerceAtMost(maximumValue: UShort): UShort { + return if (this > maximumValue) maximumValue else this +} + +/** + * Ensures that this value lies in the specified range [minimumValue]..[maximumValue]. + * + * @return this value if it's in the range, or [minimumValue] if this value is less than [minimumValue], or [maximumValue] if this value is greater than [maximumValue]. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UInt.coerceIn(minimumValue: UInt, maximumValue: UInt): UInt { + if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.") + if (this < minimumValue) return minimumValue + if (this > maximumValue) return maximumValue + return this +} + +/** + * Ensures that this value lies in the specified range [minimumValue]..[maximumValue]. + * + * @return this value if it's in the range, or [minimumValue] if this value is less than [minimumValue], or [maximumValue] if this value is greater than [maximumValue]. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun ULong.coerceIn(minimumValue: ULong, maximumValue: ULong): ULong { + if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.") + if (this < minimumValue) return minimumValue + if (this > maximumValue) return maximumValue + return this +} + +/** + * Ensures that this value lies in the specified range [minimumValue]..[maximumValue]. + * + * @return this value if it's in the range, or [minimumValue] if this value is less than [minimumValue], or [maximumValue] if this value is greater than [maximumValue]. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UByte.coerceIn(minimumValue: UByte, maximumValue: UByte): UByte { + if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.") + if (this < minimumValue) return minimumValue + if (this > maximumValue) return maximumValue + return this +} + +/** + * Ensures that this value lies in the specified range [minimumValue]..[maximumValue]. + * + * @return this value if it's in the range, or [minimumValue] if this value is less than [minimumValue], or [maximumValue] if this value is greater than [maximumValue]. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UShort.coerceIn(minimumValue: UShort, maximumValue: UShort): UShort { + if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum $maximumValue is less than minimum $minimumValue.") + if (this < minimumValue) return minimumValue + if (this > maximumValue) return maximumValue + return this +} + +/** + * Ensures that this value lies in the specified [range]. + * + * @return this value if it's in the [range], or `range.start` if this value is less than `range.start`, or `range.endInclusive` if this value is greater than `range.endInclusive`. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun UInt.coerceIn(range: ClosedRange): UInt { + if (range is ClosedFloatingPointRange) { + return this.coerceIn(range) + } + if (range.isEmpty()) throw IllegalArgumentException("Cannot coerce value to an empty range: $range.") + return when { + this < range.start -> range.start + this > range.endInclusive -> range.endInclusive + else -> this + } +} + +/** + * Ensures that this value lies in the specified [range]. + * + * @return this value if it's in the [range], or `range.start` if this value is less than `range.start`, or `range.endInclusive` if this value is greater than `range.endInclusive`. + * + * @sample samples.comparisons.ComparableOps.coerceInUnsigned + */ +@SinceKotlin("1.3") +@ExperimentalUnsignedTypes +public fun ULong.coerceIn(range: ClosedRange): ULong { + if (range is ClosedFloatingPointRange) { + return this.coerceIn(range) + } + if (range.isEmpty()) throw IllegalArgumentException("Cannot coerce value to an empty range: $range.") + return when { + this < range.start -> range.start + this > range.endInclusive -> range.endInclusive + else -> this + } +} + diff --git a/libraries/stdlib/samples/build.gradle b/libraries/stdlib/samples/build.gradle index 1117afbbcd2..df9fe8c78ba 100644 --- a/libraries/stdlib/samples/build.gradle +++ b/libraries/stdlib/samples/build.gradle @@ -10,7 +10,12 @@ sourceSets { } compileTestKotlin { - kotlinOptions.jdkHome = JDK_18 + kotlinOptions { + jdkHome = JDK_18 + freeCompilerArgs = [ + "-Xuse-experimental=kotlin.ExperimentalUnsignedTypes" + ] + } } test { diff --git a/libraries/stdlib/samples/test/samples/comparisons/comparableOps.kt b/libraries/stdlib/samples/test/samples/comparisons/comparableOps.kt index fa9de820935..3dff9990b72 100644 --- a/libraries/stdlib/samples/test/samples/comparisons/comparableOps.kt +++ b/libraries/stdlib/samples/test/samples/comparisons/comparableOps.kt @@ -28,6 +28,12 @@ class ComparableOps { assertPrints(10.coerceAtLeast(20), "20") } + @Sample + fun coerceAtLeastUnsigned() { + assertPrints(10u.coerceAtLeast(5u), "10") + assertPrints(10u.coerceAtLeast(20u), "20") + } + @Sample fun coerceAtLeastComparable() { assertPrints(DayOfWeek.WEDNESDAY.coerceAtLeast(DayOfWeek.MONDAY), "WEDNESDAY") @@ -40,6 +46,11 @@ class ComparableOps { assertPrints(10.coerceAtMost(20), "10") } + @Sample + fun coerceAtMostUnsigned() { + assertPrints(10u.coerceAtMost(5u), "5") + assertPrints(10u.coerceAtMost(20u), "10") + } @Sample fun coerceAtMostComparable() { @@ -58,6 +69,17 @@ class ComparableOps { } } + @Sample + fun coerceInUnsigned() { + assertPrints(10u.coerceIn(1u, 100u), "10") + assertPrints(10u.coerceIn(1u..100u), "10") + assertPrints(0u.coerceIn(1u, 100u), "1") + assertPrints(500u.coerceIn(1u, 100u), "100") + assertFailsWith { + 10u.coerceIn(100u, 0u) + } + } + @Sample fun coerceInComparable() { val workingDays = DayOfWeek.MONDAY..DayOfWeek.FRIDAY diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 1ef1974a47a..e31f9bc45d0 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -4289,6 +4289,20 @@ public final class kotlin/ranges/ULongRange$Companion { } public final class kotlin/ranges/URangesKt { + public static final fun coerceAtLeast-5PvTz6A (SS)S + public static final fun coerceAtLeast-J1ME1BU (II)I + public static final fun coerceAtLeast-Kr8caGY (BB)B + public static final fun coerceAtLeast-eb3DHEI (JJ)J + public static final fun coerceAtMost-5PvTz6A (SS)S + public static final fun coerceAtMost-J1ME1BU (II)I + public static final fun coerceAtMost-Kr8caGY (BB)B + public static final fun coerceAtMost-eb3DHEI (JJ)J + public static final fun coerceIn-JPwROB0 (JLkotlin/ranges/ClosedRange;)J + public static final fun coerceIn-VKSA0NQ (SSS)S + public static final fun coerceIn-WZ9TVnA (III)I + public static final fun coerceIn-b33U2AM (BBB)B + public static final fun coerceIn-sambcqE (JJJ)J + public static final fun coerceIn-wuiCnnA (ILkotlin/ranges/ClosedRange;)I public static final fun contains-68kG9v0 (Lkotlin/ranges/UIntRange;B)Z public static final fun contains-Gab390E (Lkotlin/ranges/ULongRange;I)Z public static final fun contains-ULb-yJY (Lkotlin/ranges/ULongRange;B)Z diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Comparables.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Comparables.kt index d72dd8f17b1..7498faa16c0 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Comparables.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Comparables.kt @@ -14,20 +14,42 @@ object ComparableOps : TemplateGroupBase() { specialFor(Unsigned) { since("1.3") annotation("@ExperimentalUnsignedTypes") - sourceFile(SourceFile.UComparisons) } } } + private val Family.sourceFileRanges: SourceFile + get() = when (this) { + Generic, Primitives -> SourceFile.Ranges + Unsigned -> SourceFile.URanges + else -> error(this) + } + private val Family.sourceFileComparisons: SourceFile + get() = when (this) { + Generic, Primitives -> SourceFile.Comparisons + Unsigned -> SourceFile.UComparisons + else -> error(this) + } + + private val Family.sampleSuffix: String + get() = when (this) { + Primitives -> "" + Unsigned -> "Unsigned" + Generic -> "Comparable" + else -> error(this) + } + private val numericPrimitives = PrimitiveType.numericPrimitives.sortedBy { it.capacity }.toSet() private val intPrimitives = setOf(PrimitiveType.Int, PrimitiveType.Long) private val shortIntPrimitives = setOf(PrimitiveType.Byte, PrimitiveType.Short) + private val uintPrimitives = setOf(PrimitiveType.UInt, PrimitiveType.ULong) val f_coerceAtLeast = fn("coerceAtLeast(minimumValue: SELF)") { include(Generic) include(Primitives, numericPrimitives) + include(Unsigned) } builder { - sourceFile(SourceFile.Ranges) + sourceFile(f.sourceFileRanges) returns("SELF") typeParam("T : Comparable") doc { @@ -37,7 +59,7 @@ object ComparableOps : TemplateGroupBase() { @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue] otherwise. """ } - sample("samples.comparisons.ComparableOps.coerceAtLeast${if (f == Generic) "Comparable" else ""}") + sample("samples.comparisons.ComparableOps.coerceAtLeast${f.sampleSuffix}") body { """ return if (this < minimumValue) minimumValue else this @@ -48,8 +70,9 @@ object ComparableOps : TemplateGroupBase() { val f_coerceAtMost = fn("coerceAtMost(maximumValue: SELF)") { include(Generic) include(Primitives, numericPrimitives) + include(Unsigned) } builder { - sourceFile(SourceFile.Ranges) + sourceFile(f.sourceFileRanges) returns("SELF") typeParam("T : Comparable") doc { @@ -59,7 +82,7 @@ object ComparableOps : TemplateGroupBase() { @return this value if it's less than or equal to the [maximumValue] or the [maximumValue] otherwise. """ } - sample("samples.comparisons.ComparableOps.coerceAtMost${if (f == Generic) "Comparable" else ""}") + sample("samples.comparisons.ComparableOps.coerceAtMost${f.sampleSuffix}") body { """ return if (this > maximumValue) maximumValue else this @@ -70,8 +93,9 @@ object ComparableOps : TemplateGroupBase() { val f_coerceIn_range_primitive = fn("coerceIn(range: ClosedRange)") { include(Generic) include(Primitives, intPrimitives) + include(Unsigned, uintPrimitives) } builder { - sourceFile(SourceFile.Ranges) + sourceFile(f.sourceFileRanges) returns("SELF") typeParam("T : Comparable") doc { @@ -81,21 +105,8 @@ object ComparableOps : TemplateGroupBase() { @return this value if it's in the [range], or `range.start` if this value is less than `range.start`, or `range.endInclusive` if this value is greater than `range.endInclusive`. """ } - sample("samples.comparisons.ComparableOps.coerceIn${if (f == Generic) "Comparable" else ""}") - body(Generic) { - """ - if (range is ClosedFloatingPointRange) { - return this.coerceIn(range) - } - if (range.isEmpty()) throw IllegalArgumentException("Cannot coerce value to an empty range: ${'$'}range.") - return when { - this < range.start -> range.start - this > range.endInclusive -> range.endInclusive - else -> this - } - """ - } - body(Primitives) { + sample("samples.comparisons.ComparableOps.coerceIn${f.sampleSuffix}") + body { """ if (range is ClosedFloatingPointRange) { return this.coerceIn(range) @@ -113,7 +124,7 @@ object ComparableOps : TemplateGroupBase() { val f_coerceIn_fpRange = fn("coerceIn(range: ClosedFloatingPointRange)") { include(Generic) } builder { - sourceFile(SourceFile.Ranges) + sourceFile(f.sourceFileRanges) since("1.1") returns("SELF") typeParam("T : Comparable") @@ -145,7 +156,7 @@ object ComparableOps : TemplateGroupBase() { include(Primitives, numericPrimitives) include(Unsigned) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") typeParam("T : Comparable") returns("T") @@ -210,7 +221,7 @@ object ComparableOps : TemplateGroupBase() { include(Primitives, numericPrimitives) include(Unsigned) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") typeParam("T : Comparable") returns("T") @@ -251,7 +262,7 @@ object ComparableOps : TemplateGroupBase() { val f_minOf_2_comparator = fn("minOf(a: T, b: T, comparator: Comparator)") { include(Generic) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") returns("T") receiver("") @@ -269,7 +280,7 @@ object ComparableOps : TemplateGroupBase() { val f_minOf_3_comparator = fn("minOf(a: T, b: T, c: T, comparator: Comparator)") { include(Generic) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") returns("T") receiver("") @@ -288,7 +299,7 @@ object ComparableOps : TemplateGroupBase() { include(Primitives, numericPrimitives) include(Unsigned) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") typeParam("T : Comparable") returns("T") @@ -349,7 +360,7 @@ object ComparableOps : TemplateGroupBase() { include(Primitives, numericPrimitives) include(Unsigned) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") typeParam("T : Comparable") returns("T") @@ -390,7 +401,7 @@ object ComparableOps : TemplateGroupBase() { val f_maxOf_2_comparator = fn("maxOf(a: T, b: T, comparator: Comparator)") { include(Generic) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") returns("T") receiver("") @@ -408,7 +419,7 @@ object ComparableOps : TemplateGroupBase() { val f_maxOf_3_comparator = fn("maxOf(a: T, b: T, c: T, comparator: Comparator)") { include(Generic) } builder { - sourceFile(SourceFile.Comparisons) + sourceFile(f.sourceFileComparisons) since("1.1") returns("T") receiver("") @@ -426,8 +437,9 @@ object ComparableOps : TemplateGroupBase() { val f_coerceIn_min_max = fn("coerceIn(minimumValue: SELF, maximumValue: SELF)") { include(Generic) include(Primitives, numericPrimitives) + include(Unsigned) } builder { - sourceFile(SourceFile.Ranges) + sourceFile(f.sourceFileRanges) specialFor(Generic) { signature("coerceIn(minimumValue: SELF?, maximumValue: SELF?)", notForSorting = true) } typeParam("T : Comparable") @@ -439,8 +451,8 @@ object ComparableOps : TemplateGroupBase() { @return this value if it's in the range, or [minimumValue] if this value is less than [minimumValue], or [maximumValue] if this value is greater than [maximumValue]. """ } - sample("samples.comparisons.ComparableOps.coerceIn${if (f == Generic) "Comparable" else ""}") - body(Primitives) { + sample("samples.comparisons.ComparableOps.coerceIn${f.sampleSuffix}") + body(Primitives, Unsigned) { """ if (minimumValue > maximumValue) throw IllegalArgumentException("Cannot coerce value to an empty range: maximum ${'$'}maximumValue is less than minimum ${'$'}minimumValue.") if (this < minimumValue) return minimumValue