Introduce OpenEndRange and make primitive ranges implement it

#KT-52932
This commit is contained in:
Ilya Gorbunov
2022-06-13 14:48:07 +03:00
committed by Space
parent d54b5f8e85
commit cd9b36b4c3
19 changed files with 257 additions and 16 deletions
+2
View File
@@ -47,6 +47,7 @@ compileKotlinCommon {
"-opt-in=kotlin.ExperimentalMultiplatform",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-Xallow-kotlin-package",
"-XXLanguage:+RangeUntilOperator",
]
}
}
@@ -57,6 +58,7 @@ compileTestKotlinCommon {
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi",
"-XXLanguage:+RangeUntilOperator",
]
}
}
@@ -458,6 +458,8 @@ public operator fun ClosedRange<Double>.contains(value: Float): Boolean {
return contains(value.toDouble())
}
// TODO: for OpenEndRange<Double>
/**
* Checks if the specified [value] belongs to this range.
*/
+1
View File
@@ -78,6 +78,7 @@ compileTestKotlin {
"-opt-in=kotlin.io.path.ExperimentalPathApi",
"-Xcommon-sources=${fileTree('../test').join(',')}",
"-Xsuppress-deprecated-jvm-target-warning",
"-XXLanguage:+RangeUntilOperator",
]
}
+1
View File
@@ -77,6 +77,7 @@ compileTestKotlin {
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlin.io.path.ExperimentalPathApi",
"-Xcommon-sources=${fileTree('../test').join(',')}",
"-XXLanguage:+RangeUntilOperator",
]
}
@@ -137,7 +137,8 @@ tasks.withType<KotlinCompile<*>> {
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi"
"-opt-in=kotlin.ExperimentalStdlibApi",
"-XXLanguage:+RangeUntilOperator",
)
}
+2 -1
View File
@@ -141,7 +141,8 @@ tasks.withType<KotlinCompile<*>>().configureEach {
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi"
"-opt-in=kotlin.ExperimentalStdlibApi",
"-XXLanguage:+RangeUntilOperator",
)
}
+2
View File
@@ -134,6 +134,7 @@ compileKotlin2Js {
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalMultiplatform",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-XXLanguage:+RangeUntilOperator",
]
}
}
@@ -145,6 +146,7 @@ compileTestKotlin2Js {
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi",
"-XXLanguage:+RangeUntilOperator",
]
}
}
+2
View File
@@ -115,6 +115,7 @@ compileKotlin {
"-Xuse-14-inline-classes-mangling-scheme",
"-Xsuppress-deprecated-jvm-target-warning",
"-Xbuiltins-from-sources",
"-XXLanguage:+RangeUntilOperator",
]
moduleName = "kotlin-stdlib"
}
@@ -127,6 +128,7 @@ compileTestKotlin {
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi",
"-Xsuppress-deprecated-jvm-target-warning",
"-XXLanguage:+RangeUntilOperator",
]
// This is needed for JavaTypeTest; typeOf for non-reified type parameters doesn't work otherwise, for implementation reasons.
freeCompilerArgs.remove("-Xno-optimized-callable-references")
@@ -10,9 +10,18 @@ package kotlin.ranges
/**
* A range of values of type `Char`.
*/
public class CharRange(start: Char, endInclusive: Char) : CharProgression(start, endInclusive, 1), ClosedRange<Char> {
@OptIn(ExperimentalStdlibApi::class)
public class CharRange(start: Char, endInclusive: Char) : CharProgression(start, endInclusive, 1), ClosedRange<Char>, OpenEndRange<Char> {
override val start: Char get() = first
override val endInclusive: Char get() = last
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@Deprecated("Can throw an exception when it's impossible to represent the value with Char type, for example, when the range includes MAX_VALUE. It's recommended to use 'endInclusive' property that doesn't throw.")
override val endExclusive: Char get() {
if (last == Char.MAX_VALUE) error("Cannot return the exclusive upper bound of a range that includes MAX_VALUE.")
return last + 1
}
override fun contains(value: Char): Boolean = first <= value && value <= last
@@ -41,9 +50,18 @@ public class CharRange(start: Char, endInclusive: Char) : CharProgression(start,
/**
* A range of values of type `Int`.
*/
public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int> {
@OptIn(ExperimentalStdlibApi::class)
public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int>, OpenEndRange<Int> {
override val start: Int get() = first
override val endInclusive: Int get() = last
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@Deprecated("Can throw an exception when it's impossible to represent the value with Int type, for example, when the range includes MAX_VALUE. It's recommended to use 'endInclusive' property that doesn't throw.")
override val endExclusive: Int get() {
if (last == Int.MAX_VALUE) error("Cannot return the exclusive upper bound of a range that includes MAX_VALUE.")
return last + 1
}
override fun contains(value: Int): Boolean = first <= value && value <= last
@@ -72,9 +90,18 @@ public class IntRange(start: Int, endInclusive: Int) : IntProgression(start, end
/**
* A range of values of type `Long`.
*/
public class LongRange(start: Long, endInclusive: Long) : LongProgression(start, endInclusive, 1), ClosedRange<Long> {
@OptIn(ExperimentalStdlibApi::class)
public class LongRange(start: Long, endInclusive: Long) : LongProgression(start, endInclusive, 1), ClosedRange<Long>, OpenEndRange<Long> {
override val start: Long get() = first
override val endInclusive: Long get() = last
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@Deprecated("Can throw an exception when it's impossible to represent the value with Long type, for example, when the range includes MAX_VALUE. It's recommended to use 'endInclusive' property that doesn't throw.")
override val endExclusive: Long get() {
if (last == Long.MAX_VALUE) error("Cannot return the exclusive upper bound of a range that includes MAX_VALUE.")
return last + 1
}
override fun contains(value: Long): Boolean = first <= value && value <= last
+39 -2
View File
@@ -6,10 +6,10 @@
package kotlin.ranges
/**
* Represents a range of values (for example, numbers or characters).
* Represents a range of values (for example, numbers or characters) where both the lower and upper bounds are included in the range.
* See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/ranges.html) for more information.
*/
public interface ClosedRange<T: Comparable<T>> {
public interface ClosedRange<T : Comparable<T>> {
/**
* The minimum value in the range.
*/
@@ -22,6 +22,8 @@ public interface ClosedRange<T: Comparable<T>> {
/**
* Checks whether the specified [value] belongs to the range.
*
* A value belongs to the closed range if it is greater than or equal to the [start] bound and less than or equal to the [endInclusive] bound.
*/
public operator fun contains(value: T): Boolean = value >= start && value <= endInclusive
@@ -32,3 +34,38 @@ public interface ClosedRange<T: Comparable<T>> {
*/
public fun isEmpty(): Boolean = start > endInclusive
}
/**
* Represents a range of values (for example, numbers or characters) where the upper bound is not included in the range.
* See the [Kotlin language documentation](https://kotlinlang.org/docs/reference/ranges.html) for more information.
*/
@SinceKotlin("1.7")
@ExperimentalStdlibApi
public interface OpenEndRange<T : Comparable<T>> {
/**
* The minimum value in the range.
*/
public val start: T
/**
* The maximum value in the range (exclusive).
*
* @throws IllegalStateException can be thrown if the exclusive end bound cannot be represented
* with a value of type [T].
*/
public val endExclusive: T
/**
* Checks whether the specified [value] belongs to the range.
*
* A value belongs to the open-ended range if it is greater than or equal to the [start] bound and strictly less than the [endExclusive] bound.
*/
public operator fun contains(value: T): Boolean = value >= start && value < endExclusive
/**
* Checks whether the range is empty.
*
* The open-ended range is empty if its start value is greater than or equal to the end value.
*/
public fun isEmpty(): Boolean = start >= endExclusive
}
+125 -1
View File
@@ -36,6 +36,37 @@ private open class ComparableRange<T : Comparable<T>>(
*/
public operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
/**
* Represents a range of [Comparable] values.
*/
@OptIn(ExperimentalStdlibApi::class)
private open class ComparableOpenEndRange<T : Comparable<T>>(
override val start: T,
override val endExclusive: T
) : OpenEndRange<T> {
override fun equals(other: Any?): Boolean {
return other is ComparableOpenEndRange<*> && (isEmpty() && other.isEmpty() ||
start == other.start && endExclusive == other.endExclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * start.hashCode() + endExclusive.hashCode()
}
override fun toString(): String = "$start..<$endExclusive"
}
/**
* Creates an open-ended range from this [Comparable] value to the specified [that] value.
*
* This value needs to be smaller than [that] value, otherwise the returned range will be empty.
* @sample samples.ranges.Ranges.rangeFromComparable
*/
@SinceKotlin("1.7")
@ExperimentalStdlibApi
public operator fun <T : Comparable<T>> T.rangeUntil(that: T): OpenEndRange<T> = ComparableOpenEndRange(this, that)
/**
* Represents a range of floating point numbers.
@@ -96,6 +127,47 @@ private class ClosedDoubleRange(
@SinceKotlin("1.1")
public operator fun Double.rangeTo(that: Double): ClosedFloatingPointRange<Double> = ClosedDoubleRange(this, that)
/**
* An open-ended range of values of type `Double`.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
@OptIn(ExperimentalStdlibApi::class)
private class OpenEndDoubleRange(
start: Double,
endExclusive: Double
) : OpenEndRange<Double> {
private val _start = start
private val _endExclusive = endExclusive
override val start: Double get() = _start
override val endExclusive: Double get() = _endExclusive
private fun lessThanOrEquals(a: Double, b: Double): Boolean = a <= b
override fun contains(value: Double): Boolean = value >= _start && value < _endExclusive
override fun isEmpty(): Boolean = !(_start < _endExclusive)
override fun equals(other: Any?): Boolean {
return other is OpenEndDoubleRange && (isEmpty() && other.isEmpty() ||
_start == other._start && _endExclusive == other._endExclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode()
}
override fun toString(): String = "$_start..<$_endExclusive"
}
/**
* Creates an open-ended range from this [Double] value to the specified [that] value.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
@SinceKotlin("1.7")
@ExperimentalStdlibApi
public operator fun Double.rangeUntil(that: Double): OpenEndRange<Double> = OpenEndDoubleRange(this, that)
/**
* A closed range of values of type `Float`.
@@ -138,6 +210,48 @@ private class ClosedFloatRange(
public operator fun Float.rangeTo(that: Float): ClosedFloatingPointRange<Float> = ClosedFloatRange(this, that)
/**
* An open-ended range of values of type `Float`.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
@OptIn(ExperimentalStdlibApi::class)
private class OpenEndFloatRange(
start: Float,
endExclusive: Float
) : OpenEndRange<Float> {
private val _start = start
private val _endExclusive = endExclusive
override val start: Float get() = _start
override val endExclusive: Float get() = _endExclusive
private fun lessThanOrEquals(a: Float, b: Float): Boolean = a <= b
override fun contains(value: Float): Boolean = value >= _start && value < _endExclusive
override fun isEmpty(): Boolean = !(_start < _endExclusive)
override fun equals(other: Any?): Boolean {
return other is OpenEndFloatRange && (isEmpty() && other.isEmpty() ||
_start == other._start && _endExclusive == other._endExclusive)
}
override fun hashCode(): Int {
return if (isEmpty()) -1 else 31 * _start.hashCode() + _endExclusive.hashCode()
}
override fun toString(): String = "$_start..<$_endExclusive"
}
/**
* Creates an open-ended range from this [Float] value to the specified [that] value.
*
* Numbers are compared with the ends of this range according to IEEE-754.
*/
@SinceKotlin("1.7")
@ExperimentalStdlibApi
public operator fun Float.rangeUntil(that: Float): OpenEndRange<Float> = OpenEndFloatRange(this, that)
/**
* Returns `true` if this iterable range contains the specified [element].
*
@@ -145,9 +259,19 @@ public operator fun Float.rangeTo(that: Float): ClosedFloatingPointRange<Float>
*/
@SinceKotlin("1.3")
@kotlin.internal.InlineOnly
public inline operator fun <T, R> R.contains(element: T?): Boolean where T : Any, R : Iterable<T>, R : ClosedRange<T> =
public inline operator fun <T, R> R.contains(element: T?): Boolean where T : Any, R : ClosedRange<T>, R : Iterable<T> =
element != null && contains(element)
/**
* Returns `true` if this iterable range contains the specified [element].
*
* Always returns `false` if the [element] is `null`.
*/
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline operator fun <T, R> R.contains(element: T?): Boolean where T : Any, R : OpenEndRange<T>, R : Iterable<T> =
element != null && contains(element)
internal fun checkStepIsPositive(isPositive: Boolean, step: Number) {
if (!isPositive) throw IllegalArgumentException("Step must be positive, was: $step.")
@@ -16,9 +16,18 @@ import kotlin.internal.*
*/
@SinceKotlin("1.5")
@WasExperimental(ExperimentalUnsignedTypes::class)
public class UIntRange(start: UInt, endInclusive: UInt) : UIntProgression(start, endInclusive, 1), ClosedRange<UInt> {
@OptIn(ExperimentalStdlibApi::class)
public class UIntRange(start: UInt, endInclusive: UInt) : UIntProgression(start, endInclusive, 1), ClosedRange<UInt>, OpenEndRange<UInt> {
override val start: UInt get() = first
override val endInclusive: UInt get() = last
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@Deprecated("Can throw an exception when it's impossible to represent the value with UInt type, for example, when the range includes MAX_VALUE. It's recommended to use 'endInclusive' property that doesn't throw.")
override val endExclusive: UInt get() {
if (last == UInt.MAX_VALUE) error("Cannot return the exclusive upper bound of a range that includes MAX_VALUE.")
return last + 1u
}
override fun contains(value: UInt): Boolean = first <= value && value <= last
@@ -16,9 +16,18 @@ import kotlin.internal.*
*/
@SinceKotlin("1.5")
@WasExperimental(ExperimentalUnsignedTypes::class)
public class ULongRange(start: ULong, endInclusive: ULong) : ULongProgression(start, endInclusive, 1), ClosedRange<ULong> {
@OptIn(ExperimentalStdlibApi::class)
public class ULongRange(start: ULong, endInclusive: ULong) : ULongProgression(start, endInclusive, 1), ClosedRange<ULong>, OpenEndRange<ULong> {
override val start: ULong get() = first
override val endInclusive: ULong get() = last
@SinceKotlin("1.7")
@ExperimentalStdlibApi
@Deprecated("Can throw an exception when it's impossible to represent the value with ULong type, for example, when the range includes MAX_VALUE. It's recommended to use 'endInclusive' property that doesn't throw.")
override val endExclusive: ULong get() {
if (last == ULong.MAX_VALUE) error("Cannot return the exclusive upper bound of a range that includes MAX_VALUE.")
return last + 1u
}
override fun contains(value: ULong): Boolean = first <= value && value <= last
+2 -1
View File
@@ -117,7 +117,8 @@ tasks.withType<KotlinCompile<*>>().configureEach {
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalUnsignedTypes",
"-opt-in=kotlin.ExperimentalStdlibApi"
"-opt-in=kotlin.ExperimentalStdlibApi",
"-XXLanguage:+RangeUntilOperator",
)
}