[stdlib] KT-48530 Make equals and hashCode symmetric for ranges and progressions
diff --git a/generators/builtins/ranges.kt b/generators/builtins/ranges.kt index 009e813da..8223b2f 100644 --- a/generators/builtins/ranges.kt +++ b/generators/builtins/ranges.kt
@@ -63,12 +63,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is $range && (isEmpty() && other.isEmpty() || - ${compare("first")} && ${compare("last")}) - - override fun hashCode(): Int $hashCode - override fun toString(): String = $toString public companion object {
diff --git a/generators/builtins/unsignedTypes.kt b/generators/builtins/unsignedTypes.kt index 8847c5f..0bc1546 100644 --- a/generators/builtins/unsignedTypes.kt +++ b/generators/builtins/unsignedTypes.kt
@@ -596,13 +596,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is ${elementType}Range && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else (31 * ${hashCodeConversion("first")}.toInt() + ${hashCodeConversion("last")}.toInt()) - override fun toString(): String = "${'$'}first..${'$'}last" public companion object {
diff --git a/libraries/stdlib/src/kotlin/ranges/PrimitiveRanges.kt b/libraries/stdlib/src/kotlin/ranges/PrimitiveRanges.kt index e52fe1a..0cba21b 100644 --- a/libraries/stdlib/src/kotlin/ranges/PrimitiveRanges.kt +++ b/libraries/stdlib/src/kotlin/ranges/PrimitiveRanges.kt
@@ -33,13 +33,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is CharRange && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else (31 * first.code + last.code) - override fun toString(): String = "$first..$last" public companion object { @@ -72,13 +65,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is IntRange && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else (31 * first + last) - override fun toString(): String = "$first..$last" public companion object { @@ -111,13 +97,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is LongRange && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else 31 * first.hashCode() + last.hashCode() - override fun toString(): String = "$first..$last" public companion object {
diff --git a/libraries/stdlib/test/ranges/RangeTest.kt b/libraries/stdlib/test/ranges/RangeTest.kt index 16944d3..9ff284e 100644 --- a/libraries/stdlib/test/ranges/RangeTest.kt +++ b/libraries/stdlib/test/ranges/RangeTest.kt
@@ -344,80 +344,69 @@ assertTrue(("range".."progression").isEmpty()) } - @Suppress("ReplaceAssertBooleanWithAssertEquality", "EmptyRange") - @Test fun emptyEquals() { - assertTrue(IntRange.EMPTY == IntRange.EMPTY) - assertEquals(IntRange.EMPTY, IntRange.EMPTY) - assertEquals(0L..42L, 0L..42L) - assertEquals(0L..4200000042000000L, 0L..4200000042000000L) - assertEquals(3 downTo 0, 3 downTo 0) - - assertEquals(2..1, 1..0) - assertEquals(2L..1L, 1L..0L) - assertEquals(2.toShort()..1.toShort(), 1.toShort()..0.toShort()) - assertEquals(2.toByte()..1.toByte(), 1.toByte()..0.toByte()) - assertEquals(0f..-3.14f, 3.14f..0f) - assertEquals(-2.0..-3.0, 3.0..2.0) - assertEquals(0.0..Double.NaN, 1.0..0.0) - assertEquals(0.0F..Float.NaN, 1.0F..0.0F) - assertEquals('b'..'a', 'c'..'b') - - assertTrue(1 downTo 2 == 2 downTo 3) - assertTrue(-1L downTo 0L == -2L downTo -1L) - assertEquals('j'..'a' step 4, 'u'..'q' step 2) - - assertFalse(0..1 == IntRange.EMPTY) - - assertEquals("range".."progression", "hashcode".."equals") - assertFalse(("aa".."bb") == ("aaa".."bbb")) + private fun <T> assertAllEqual(vararg elements: T) { + for (i in 0..<elements.size) { + assertEquals(elements[i], elements[i]) + for (j in i + 1..<elements.size) { + assertEquals(elements[i], elements[j]) + assertEquals(elements[j], elements[i]) + assertEquals(elements[i].hashCode(), elements[j].hashCode()) + } + } } @Suppress("EmptyRange") - @Test fun emptyHashCode() { - assertEquals((0..42).hashCode(), (0..42).hashCode()) - assertEquals((1.23..4.56).hashCode(), (1.23..4.56).hashCode()) + @Test fun emptyEqualsHashCode() { + assertAllEqual(IntRange.EMPTY, 2..1, 3..<3, 1..<1, 5..4 step 1, 5..4 step 5, 0 downTo 3 step 2) + assertAllEqual(LongRange.EMPTY, 2L..1L, 3L..<3L, 1L..<1L, 5L..4L step 1, 5L..4L step 5, 0L downTo 3L step 2) + assertAllEqual(CharRange.EMPTY, 'b'..'a', 'c'..<'b', 'a'..<'a', 'e'..'d' step 2, 'e'..'d' step 5, 'a' downTo 'c' step 2) + assertAllEqual(0f..-3.14f, 3.14f..0f, 0.0F..Float.NaN, Float.NaN..Float.MAX_VALUE) + assertAllEqual(-2.0..-3.0, 3.0..2.0, 0.0..Double.NaN, Double.NaN..Double.MAX_VALUE) + assertAllEqual("range".."progression", "hashcode".."equals") - assertEquals((0..-1).hashCode(), IntRange.EMPTY.hashCode()) - assertEquals((2L..1L).hashCode(), (1L..0L).hashCode()) - assertEquals((0.toShort()..(-1).toShort()).hashCode(), (42.toShort()..0.toShort()).hashCode()) - assertEquals((0.toByte()..(-1).toByte()).hashCode(), (42.toByte()..0.toByte()).hashCode()) - assertEquals((0f..-3.14f).hashCode(), (2.39f..1.41f).hashCode()) - assertEquals((0.0..-10.0).hashCode(), (10.0..0.0).hashCode()) - assertEquals(('z'..'x').hashCode(), ('l'..'k').hashCode()) - - assertEquals((1 downTo 2).hashCode(), (2 downTo 3).hashCode()) - assertEquals((1L downTo 2L).hashCode(), (2L downTo 3L).hashCode()) - assertEquals(('a' downTo 'b').hashCode(), ('c' downTo 'd').hashCode()) - - assertEquals(("range".."progression").hashCode(), ("hashcode".."equals").hashCode()) + assertNotEquals(0..1, IntRange.EMPTY) + assertNotEquals(0L..1L, LongRange.EMPTY) + assertNotEquals('a'..'b', CharRange.EMPTY) + assertNotEquals("aa".."bb", "bb".."aa") } @Suppress("EmptyRange") @Test fun emptyOpenEquals() { - assertEquals(0..<0, 1..<1) - assertEquals(0..-1, 1..<1) - assertEquals(0L..<0L, 1L..<1L) - assertEquals(0L..-1L, 1L..<1L) - - assertEquals(0u..<0u, 1u..<1u) - assertEquals(1u..0u, 2u..<2u) - assertEquals(0uL..<0uL, 1uL..<1uL) - assertEquals(1uL..0uL, 2uL..<2uL) - assertEquals(Double.NaN..<0.0, 0.0..<0.0) assertEquals(0.0F..<Float.NaN, 0.0F..<0.0F) + assertNotEquals<Any>(1.0..0.0, 1.0..<0.0) assertNotEquals<Any>(1.0F..0.0F, 1.0F..<0.0F) } @Test + fun nonEmptyEquals() { + assertAllEqual(0..42, 0..<43, 0..42 step 1) + assertAllEqual(0L..4200000042000000L, 0L..<4200000042000001L, 0L..4200000042000000L step 1) + assertAllEqual('a'..'d', 'a'..<'e', 'a'..'d' step 1) + + assertAllEqual(0.0..1.0, 0.0..1.0) + assertAllEqual(0.0F..1.0F, 0.0F..1.0F) + + assertAllEqual(3 downTo 0, 3 downTo 0 step 1) + assertAllEqual(3L downTo 0L, 3L downTo 0L step 1) + assertAllEqual('d' downTo 'a', 'd' downTo 'a' step 1) + } + + @Test fun nonEmptyRangeHashCode() { + fun <N : Comparable<N>, R> checkIterableRangeHashCode(range: R) where R : ClosedRange<N>, R : Iterable<N> { + assertEquals(31 * 31 * range.start.hashCode() + 31 * range.endInclusive.hashCode() + 1, range.hashCode()) + } fun <N : Comparable<N>> checkHashCode(range: ClosedRange<N>) { assertEquals(31 * range.start.hashCode() + range.endInclusive.hashCode(), range.hashCode()) } - checkHashCode(1u..10u) - checkHashCode(1uL..10uL) - checkHashCode('a'..'z') + checkIterableRangeHashCode(1..10) + checkIterableRangeHashCode(1L..10L) + checkIterableRangeHashCode('a'..'z') + checkHashCode(1.0..2.0) + checkHashCode(1.0f..2.0f) + checkHashCode("a".."bz") } @Test
diff --git a/libraries/stdlib/test/ranges/URangeTest.kt b/libraries/stdlib/test/ranges/URangeTest.kt index f259c01..ea969ff 100644 --- a/libraries/stdlib/test/ranges/URangeTest.kt +++ b/libraries/stdlib/test/ranges/URangeTest.kt
@@ -196,48 +196,34 @@ assertFalse((2uL downTo 0uL).isEmpty()) } - @Suppress("ReplaceAssertBooleanWithAssertEquality", "EmptyRange") - @Test - fun emptyEquals() { - assertTrue(UIntRange.EMPTY == UIntRange.EMPTY) - assertEquals(UIntRange.EMPTY, UIntRange.EMPTY) - assertEquals(0uL..42uL, 0uL..42uL) - assertEquals(0uL..4200000042000000uL, 0uL..4200000042000000uL) - assertEquals(3u downTo 0u, 3u downTo 0u) - - assertEquals(2u..1u, 1u..0u) - assertEquals(2uL..1uL, 1uL..0uL) - assertEquals(2u.toUShort()..1u.toUShort(), 1u.toUShort()..0u.toUShort()) - assertEquals(2u.toUByte()..1u.toUByte(), 1u.toUByte()..0u.toUByte()) - - assertTrue(1u downTo 2u == 2u downTo 3u) - assertTrue(0uL downTo (-1).toULong() == (-2).toULong() downTo (-1).toULong()) - - assertFalse(0u..1u == UIntRange.EMPTY) - assertTrue(0u..<0u == UIntRange.EMPTY) + private fun <T> assertAllEqual(vararg elements: T) { + for (i in 0..<elements.size) { + assertEquals(elements[i], elements[i]) + for (j in i + 1..<elements.size) { + assertEquals(elements[i], elements[j]) + assertEquals(elements[j], elements[i]) + assertEquals(elements[i].hashCode(), elements[j].hashCode()) + } + } } @Suppress("EmptyRange") @Test - fun emptyHashCode() { - assertEquals((0u..42u).hashCode(), (0u..42u).hashCode()) + fun emptyEqualsHashCode() { + assertAllEqual(UIntRange.EMPTY, 2u..1u, 3u..<3u, 1u..<1u, 5u..4u step 1, 5u..4u step 5, 0u downTo 3u step 2) + assertAllEqual(ULongRange.EMPTY, 2uL..1uL, 3uL..<3uL, 1uL..<1uL, 5uL..4uL step 1, 5uL..4uL step 5, 0uL downTo 3uL step 2) - assertEquals(((-1).toUInt()..0u).hashCode(), UIntRange.EMPTY.hashCode()) - assertEquals((2uL..1uL).hashCode(), (1uL..0uL).hashCode()) - assertEquals(((-1).toUShort()..(-2).toUShort()).hashCode(), (42.toUShort()..0.toUShort()).hashCode()) - assertEquals(((-1).toUByte()..(-2).toUByte()).hashCode(), (42.toUByte()..0.toUByte()).hashCode()) - - assertEquals((1u downTo 2u).hashCode(), (2u downTo 3U).hashCode()) - assertEquals((1UL downTo 2uL).hashCode(), (2UL downTo 3UL).hashCode()) + assertNotEquals(0u..1u, UIntRange.EMPTY) + assertNotEquals(0uL..1uL, ULongRange.EMPTY) } @Test fun nonEmptyRangeHashCode() { - fun <N : Comparable<N>> checkHashCode(range: ClosedRange<N>) { - assertEquals(31 * range.start.hashCode() + range.endInclusive.hashCode(), range.hashCode()) + fun <N : Comparable<N>, R> checkIterableRangeHashCode(range: R) where R : ClosedRange<N>, R : Iterable<N> { + assertEquals(31 * 31 * range.start.hashCode() + 31 * range.endInclusive.hashCode() + 1, range.hashCode()) } - checkHashCode(1u..10u) - checkHashCode(1uL..10uL) + checkIterableRangeHashCode(1u..10u) + checkIterableRangeHashCode(1uL..10uL) } @Test
diff --git a/libraries/stdlib/unsigned/src/kotlin/UIntRange.kt b/libraries/stdlib/unsigned/src/kotlin/UIntRange.kt index e544d41..dc7100f 100644 --- a/libraries/stdlib/unsigned/src/kotlin/UIntRange.kt +++ b/libraries/stdlib/unsigned/src/kotlin/UIntRange.kt
@@ -37,13 +37,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is UIntRange && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else (31 * first.toInt() + last.toInt()) - override fun toString(): String = "$first..$last" public companion object {
diff --git a/libraries/stdlib/unsigned/src/kotlin/ULongRange.kt b/libraries/stdlib/unsigned/src/kotlin/ULongRange.kt index d8128f3..536c476 100644 --- a/libraries/stdlib/unsigned/src/kotlin/ULongRange.kt +++ b/libraries/stdlib/unsigned/src/kotlin/ULongRange.kt
@@ -37,13 +37,6 @@ */ override fun isEmpty(): Boolean = first > last - override fun equals(other: Any?): Boolean = - other is ULongRange && (isEmpty() && other.isEmpty() || - first == other.first && last == other.last) - - override fun hashCode(): Int = - if (isEmpty()) -1 else (31 * (first xor (first shr 32)).toInt() + (last xor (last shr 32)).toInt()) - override fun toString(): String = "$first..$last" public companion object {
diff --git a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api index c3302df..359748c 100644 --- a/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api +++ b/libraries/tools/binary-compatibility-validator/klib-public-api/kotlin-stdlib.api
@@ -1712,8 +1712,6 @@ final fun <get-start>(): kotlin/Char // kotlin.ranges/CharRange.start.<get-start>|<get-start>(){}[0] final fun contains(kotlin/Char): kotlin/Boolean // kotlin.ranges/CharRange.contains|contains(kotlin.Char){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // kotlin.ranges/CharRange.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlin.ranges/CharRange.hashCode|hashCode(){}[0] final fun isEmpty(): kotlin/Boolean // kotlin.ranges/CharRange.isEmpty|isEmpty(){}[0] final fun toString(): kotlin/String // kotlin.ranges/CharRange.toString|toString(){}[0] @@ -1734,8 +1732,6 @@ final fun <get-start>(): kotlin/Int // kotlin.ranges/IntRange.start.<get-start>|<get-start>(){}[0] final fun contains(kotlin/Int): kotlin/Boolean // kotlin.ranges/IntRange.contains|contains(kotlin.Int){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // kotlin.ranges/IntRange.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlin.ranges/IntRange.hashCode|hashCode(){}[0] final fun isEmpty(): kotlin/Boolean // kotlin.ranges/IntRange.isEmpty|isEmpty(){}[0] final fun toString(): kotlin/String // kotlin.ranges/IntRange.toString|toString(){}[0] @@ -1756,8 +1752,6 @@ final fun <get-start>(): kotlin/Long // kotlin.ranges/LongRange.start.<get-start>|<get-start>(){}[0] final fun contains(kotlin/Long): kotlin/Boolean // kotlin.ranges/LongRange.contains|contains(kotlin.Long){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // kotlin.ranges/LongRange.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlin.ranges/LongRange.hashCode|hashCode(){}[0] final fun isEmpty(): kotlin/Boolean // kotlin.ranges/LongRange.isEmpty|isEmpty(){}[0] final fun toString(): kotlin/String // kotlin.ranges/LongRange.toString|toString(){}[0] @@ -1778,8 +1772,6 @@ final fun <get-start>(): kotlin/UInt // kotlin.ranges/UIntRange.start.<get-start>|<get-start>(){}[0] final fun contains(kotlin/UInt): kotlin/Boolean // kotlin.ranges/UIntRange.contains|contains(kotlin.UInt){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // kotlin.ranges/UIntRange.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlin.ranges/UIntRange.hashCode|hashCode(){}[0] final fun isEmpty(): kotlin/Boolean // kotlin.ranges/UIntRange.isEmpty|isEmpty(){}[0] final fun toString(): kotlin/String // kotlin.ranges/UIntRange.toString|toString(){}[0] @@ -1800,8 +1792,6 @@ final fun <get-start>(): kotlin/ULong // kotlin.ranges/ULongRange.start.<get-start>|<get-start>(){}[0] final fun contains(kotlin/ULong): kotlin/Boolean // kotlin.ranges/ULongRange.contains|contains(kotlin.ULong){}[0] - final fun equals(kotlin/Any?): kotlin/Boolean // kotlin.ranges/ULongRange.equals|equals(kotlin.Any?){}[0] - final fun hashCode(): kotlin/Int // kotlin.ranges/ULongRange.hashCode|hashCode(){}[0] final fun isEmpty(): kotlin/Boolean // kotlin.ranges/ULongRange.isEmpty|isEmpty(){}[0] final fun toString(): kotlin/String // kotlin.ranges/ULongRange.toString|toString(){}[0]
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 18b89d1..f921064 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
@@ -4732,14 +4732,12 @@ public fun <init> (CC)V public fun contains (C)Z public synthetic fun contains (Ljava/lang/Comparable;)Z - public fun equals (Ljava/lang/Object;)Z public fun getEndExclusive ()Ljava/lang/Character; public synthetic fun getEndExclusive ()Ljava/lang/Comparable; public fun getEndInclusive ()Ljava/lang/Character; public synthetic fun getEndInclusive ()Ljava/lang/Comparable; public fun getStart ()Ljava/lang/Character; public synthetic fun getStart ()Ljava/lang/Comparable; - public fun hashCode ()I public fun isEmpty ()Z public fun toString ()Ljava/lang/String; } @@ -4793,14 +4791,12 @@ public fun <init> (II)V public fun contains (I)Z public synthetic fun contains (Ljava/lang/Comparable;)Z - public fun equals (Ljava/lang/Object;)Z public synthetic fun getEndExclusive ()Ljava/lang/Comparable; public fun getEndExclusive ()Ljava/lang/Integer; public synthetic fun getEndInclusive ()Ljava/lang/Comparable; public fun getEndInclusive ()Ljava/lang/Integer; public synthetic fun getStart ()Ljava/lang/Comparable; public fun getStart ()Ljava/lang/Integer; - public fun hashCode ()I public fun isEmpty ()Z public fun toString ()Ljava/lang/String; } @@ -4831,14 +4827,12 @@ public fun <init> (JJ)V public fun contains (J)Z public synthetic fun contains (Ljava/lang/Comparable;)Z - public fun equals (Ljava/lang/Object;)Z public synthetic fun getEndExclusive ()Ljava/lang/Comparable; public fun getEndExclusive ()Ljava/lang/Long; public synthetic fun getEndInclusive ()Ljava/lang/Comparable; public fun getEndInclusive ()Ljava/lang/Long; public synthetic fun getStart ()Ljava/lang/Comparable; public fun getStart ()Ljava/lang/Long; - public fun hashCode ()I public fun isEmpty ()Z public fun toString ()Ljava/lang/String; } @@ -5015,14 +5009,12 @@ public synthetic fun <init> (IILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun contains (Ljava/lang/Comparable;)Z public fun contains-WZ4Q5Ns (I)Z - public fun equals (Ljava/lang/Object;)Z public synthetic fun getEndExclusive ()Ljava/lang/Comparable; public fun getEndExclusive-pVg5ArA ()I public synthetic fun getEndInclusive ()Ljava/lang/Comparable; public fun getEndInclusive-pVg5ArA ()I public synthetic fun getStart ()Ljava/lang/Comparable; public fun getStart-pVg5ArA ()I - public fun hashCode ()I public fun isEmpty ()Z public fun toString ()Ljava/lang/String; } @@ -5052,14 +5044,12 @@ public synthetic fun <init> (JJLkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun contains (Ljava/lang/Comparable;)Z public fun contains-VKZWuLQ (J)Z - public fun equals (Ljava/lang/Object;)Z public synthetic fun getEndExclusive ()Ljava/lang/Comparable; public fun getEndExclusive-s-VKNKU ()J public synthetic fun getEndInclusive ()Ljava/lang/Comparable; public fun getEndInclusive-s-VKNKU ()J public synthetic fun getStart ()Ljava/lang/Comparable; public fun getStart-s-VKNKU ()J - public fun hashCode ()I public fun isEmpty ()Z public fun toString ()Ljava/lang/String; }