diff --git a/compiler/testData/codegen/box/ranges/contains/inComparableRange.kt b/compiler/testData/codegen/box/ranges/contains/inComparableRange.kt index b74fd27a010..52103d1cdc4 100644 --- a/compiler/testData/codegen/box/ranges/contains/inComparableRange.kt +++ b/compiler/testData/codegen/box/ranges/contains/inComparableRange.kt @@ -12,7 +12,12 @@ class ComparablePair>(val first: T, val second: T) : Comparabl fun > genericRangeTo(start: T, endInclusive: T) = start..endInclusive operator fun Double.rangeTo(other: Double) = genericRangeTo(this, other) -operator fun Float.rangeTo(other: Float) = ClosedFloatRange(this, other) +// some weird inverted range +operator fun Float.rangeTo(other: Float) = object : ClosedComparableRange { + override val endInclusive: Float = this@rangeTo + override val start: Float = other + override fun lessThanOrEquals(a: Float, b: Float) = a >= b +} fun check(x: Double, left: Double, right: Double): Boolean { val result = x in left..right diff --git a/js/js.libraries/src/core/ranges.kt b/js/js.libraries/src/core/ranges.kt index a73e137a5d8..75b71fb047b 100644 --- a/js/js.libraries/src/core/ranges.kt +++ b/js/js.libraries/src/core/ranges.kt @@ -17,5 +17,5 @@ package kotlin.ranges @SinceKotlin("1.1") -public inline operator fun Float.rangeTo(that: Float): ClosedRange = - this.toDouble().rangeTo(that.toDouble()) as ClosedRange +public inline operator fun Float.rangeTo(that: Float): ClosedComparableRange = + this.toDouble().rangeTo(that.toDouble()).unsafeCast>() diff --git a/libraries/stdlib/src/kotlin/ranges/Ranges.kt b/libraries/stdlib/src/kotlin/ranges/Ranges.kt index b6f2fb122bf..146772e9596 100644 --- a/libraries/stdlib/src/kotlin/ranges/Ranges.kt +++ b/libraries/stdlib/src/kotlin/ranges/Ranges.kt @@ -2,6 +2,13 @@ @file:kotlin.jvm.JvmName("RangesKt") package kotlin.ranges +public interface ClosedComparableRange> : ClosedRange { + override fun contains(value: T): Boolean = lessThanOrEquals(value, start) && lessThanOrEquals(value, endInclusive) + override fun isEmpty(): Boolean = !lessThanOrEquals(start, endInclusive) + + fun lessThanOrEquals(a: T, b: T): Boolean +} + /** * Represents a range of [Comparable] values. */ @@ -22,20 +29,27 @@ private open class ComparableRange> ( override fun toString(): String = "$start..$endInclusive" } -private class DoubleRange ( +/** + * A closed range of values of type `Double`. + * + * Numbers are compared with the ends of this range according to IEEE-754. + */ +private class ClosedDoubleRange ( start: Double, endInclusive: Double -): ClosedRange { +) : ClosedComparableRange { private val _start = start private val _endInclusive = endInclusive override val start: Double get() = _start override val endInclusive: Double get() = _endInclusive + override fun lessThanOrEquals(a: Double, b: Double): Boolean = a <= b + override fun contains(value: Double): Boolean = value >= _start && value <= _endInclusive override fun isEmpty(): Boolean = !(_start <= _endInclusive) override fun equals(other: Any?): Boolean { - return other is DoubleRange && (isEmpty() && other.isEmpty() || + return other is ClosedDoubleRange && (isEmpty() && other.isEmpty() || _start == other._start && _endInclusive == other._endInclusive) } @@ -45,21 +59,29 @@ private class DoubleRange ( override fun toString(): String = "$_start..$_endInclusive" } +/** + * A closed range of values of type `Float`. + * + * Numbers are compared with the ends of this range according to IEEE-754. + + */ @JvmVersion -private class FloatRange ( +private class ClosedFloatRange ( start: Float, endInclusive: Float -): ClosedRange { +): ClosedComparableRange { private val _start = start private val _endInclusive = endInclusive override val start: Float get() = _start override val endInclusive: Float get() = _endInclusive + override fun lessThanOrEquals(a: Float, b: Float): Boolean = a <= b + override fun contains(value: Float): Boolean = value >= _start && value <= _endInclusive override fun isEmpty(): Boolean = !(_start <= _endInclusive) override fun equals(other: Any?): Boolean { - return other is FloatRange && (isEmpty() && other.isEmpty() || + return other is ClosedFloatRange && (isEmpty() && other.isEmpty() || _start == other._start && _endInclusive == other._endInclusive) } @@ -80,14 +102,14 @@ public operator fun > T.rangeTo(that: T): ClosedRange = Comp * Creates a range from this [Double] value to the specified [other] value. */ @SinceKotlin("1.1") -public operator fun Double.rangeTo(that: Double): ClosedRange = DoubleRange(this, that) +public operator fun Double.rangeTo(that: Double): ClosedComparableRange = ClosedDoubleRange(this, that) /** * Creates a range from this [Float] value to the specified [other] value. */ @JvmVersion @SinceKotlin("1.1") -public operator fun Float.rangeTo(that: Float): ClosedRange = FloatRange(this, that) +public operator fun Float.rangeTo(that: Float): ClosedComparableRange = ClosedFloatRange(this, that) internal fun checkStepIsPositive(isPositive: Boolean, step: Number) { diff --git a/libraries/stdlib/test/ranges/CoercionTest.kt b/libraries/stdlib/test/ranges/CoercionTest.kt index 21975989cea..69538a11d49 100644 --- a/libraries/stdlib/test/ranges/CoercionTest.kt +++ b/libraries/stdlib/test/ranges/CoercionTest.kt @@ -84,6 +84,8 @@ class CoercionTest { assertFails { 1.0.coerceIn(1.0, 0.0) } assertFails { 1.0.coerceIn(1.0..0.0) } + assertTrue(0.0 == 0.0.coerceIn(0.0, -0.0)) + assertTrue(0.0 == 0.0.coerceIn(0.0..-0.0)) } @Test diff --git a/libraries/stdlib/test/ranges/RangeTest.kt b/libraries/stdlib/test/ranges/RangeTest.kt index fdb2dcd4b78..177c4a8c3f9 100644 --- a/libraries/stdlib/test/ranges/RangeTest.kt +++ b/libraries/stdlib/test/ranges/RangeTest.kt @@ -197,14 +197,18 @@ public class RangeTest { assertTrue(1.toLong() in range) assertTrue(1.toFloat() in range) - val specialRange = 0.0..-0.0 - assertFalse(specialRange.isEmpty()) - assertTrue(-0.0 in specialRange) - val normalSpecialRange = -0.0..0.0 - assertEquals(specialRange, normalSpecialRange) - assertEquals(specialRange.hashCode(), normalSpecialRange.hashCode()) + val zeroRange = 0.0..-0.0 + assertFalse(zeroRange.isEmpty()) + assertTrue(-0.0 in zeroRange) + assertTrue(-0.0F in zeroRange) + val normalZeroRange = -0.0..0.0 + assertEquals(zeroRange, normalZeroRange) + assertEquals(zeroRange.hashCode(), normalZeroRange.hashCode()) + val nanRange = 0.0..Double.NaN + assertFalse(1.0 in nanRange) assertFalse(Double.NaN in nanRange) + assertFalse(Float.NaN in nanRange) assertTrue(nanRange.isEmpty()) val halfInfRange = 0.0..Double.POSITIVE_INFINITY @@ -241,13 +245,15 @@ public class RangeTest { assertFalse(Double.MAX_VALUE in range) - val specialRange = 0.0F..-0.0F - assertFalse(specialRange.isEmpty()) - assertTrue(-0.0F in specialRange) - val normalSpecialRange = -0.0F..0.0F - assertEquals(specialRange, normalSpecialRange) - assertEquals(specialRange.hashCode(), normalSpecialRange.hashCode()) + val zeroRange = 0.0F..-0.0F + assertFalse(zeroRange.isEmpty()) + assertTrue(-0.0F in zeroRange) + val normalZeroRange = -0.0F..0.0F + assertEquals(zeroRange, normalZeroRange) + assertEquals(zeroRange.hashCode(), normalZeroRange.hashCode()) + val nanRange = 0.0F..Float.NaN + assertFalse(1.0F in nanRange) assertFalse(Float.NaN in nanRange) assertTrue(nanRange.isEmpty()) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index a2eb77856d4..9020828ac25 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -1865,6 +1865,17 @@ public abstract interface class kotlin/properties/ReadWriteProperty { public abstract fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V } +public abstract interface class kotlin/ranges/ClosedComparableRange : kotlin/ranges/ClosedRange { + public abstract fun contains (Ljava/lang/Comparable;)Z + public abstract fun isEmpty ()Z + public abstract fun lessThanOrEquals (Ljava/lang/Comparable;Ljava/lang/Comparable;)Z +} + +public final class kotlin/ranges/ClosedComparableRange$DefaultImpls { + public static fun contains (Lkotlin/ranges/ClosedComparableRange;Ljava/lang/Comparable;)Z + public static fun isEmpty (Lkotlin/ranges/ClosedComparableRange;)Z +} + public final class kotlin/ranges/RangesKt { public static final fun byteRangeContains (Lkotlin/ranges/ClosedRange;D)Z public static final fun byteRangeContains (Lkotlin/ranges/ClosedRange;F)Z @@ -1932,8 +1943,8 @@ public final class kotlin/ranges/RangesKt { public static final fun longRangeContains (Lkotlin/ranges/ClosedRange;F)Z public static final fun longRangeContains (Lkotlin/ranges/ClosedRange;I)Z public static final fun longRangeContains (Lkotlin/ranges/ClosedRange;S)Z - public static final fun rangeTo (DD)Lkotlin/ranges/ClosedRange; - public static final fun rangeTo (FF)Lkotlin/ranges/ClosedRange; + public static final fun rangeTo (DD)Lkotlin/ranges/ClosedComparableRange; + public static final fun rangeTo (FF)Lkotlin/ranges/ClosedComparableRange; public static final fun rangeTo (Ljava/lang/Comparable;Ljava/lang/Comparable;)Lkotlin/ranges/ClosedRange; public static final fun reversed (Lkotlin/ranges/CharProgression;)Lkotlin/ranges/CharProgression; public static final fun reversed (Lkotlin/ranges/IntProgression;)Lkotlin/ranges/IntProgression;