From ad23a2d05b2dadc91b1fd3dca501783d6baaef60 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 16 Jun 2014 19:12:00 +0400 Subject: [PATCH] Fix equals and hashCode for empty ranges and progressions Empty range is equal to any other empty range --- core/builtins/src/kotlin/Progressions.kt | 40 +++++++++++----- core/builtins/src/kotlin/Ranges.kt | 40 +++++++++++----- .../jet/generators/builtins/progressions.kt | 16 +++++-- .../jet/generators/builtins/ranges.kt | 16 +++++-- libraries/stdlib/src/kotlin/Ranges.kt | 13 ++++- libraries/stdlib/test/language/RangeTest.kt | 47 +++++++++++++++++++ 6 files changed, 134 insertions(+), 38 deletions(-) diff --git a/core/builtins/src/kotlin/Progressions.kt b/core/builtins/src/kotlin/Progressions.kt index 3277ab523d2..4d35c7ea78a 100644 --- a/core/builtins/src/kotlin/Progressions.kt +++ b/core/builtins/src/kotlin/Progressions.kt @@ -32,9 +32,11 @@ public class ByteProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is ByteProgression && start == other.start && end == other.end && increment == other.increment + other is ByteProgression && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end && increment == other.increment) - override fun hashCode(): Int = 31 * (31 * start.toInt() + end) + increment + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * start.toInt() + end) + increment) override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -53,9 +55,11 @@ public class CharProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is CharProgression && start == other.start && end == other.end && increment == other.increment + other is CharProgression && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end && increment == other.increment) - override fun hashCode(): Int = 31 * (31 * start.toInt() + end) + increment + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * start.toInt() + end) + increment) override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -74,9 +78,11 @@ public class ShortProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is ShortProgression && start == other.start && end == other.end && increment == other.increment + other is ShortProgression && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end && increment == other.increment) - override fun hashCode(): Int = 31 * (31 * start.toInt() + end) + increment + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * start.toInt() + end) + increment) override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -95,9 +101,11 @@ public class IntProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is IntProgression && start == other.start && end == other.end && increment == other.increment + other is IntProgression && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end && increment == other.increment) - override fun hashCode(): Int = 31 * (31 * start + end) + increment + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * start + end) + increment) override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -116,9 +124,11 @@ public class LongProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is LongProgression && start == other.start && end == other.end && increment == other.increment + other is LongProgression && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end && increment == other.increment) - override fun hashCode(): Int = (31 * (31 * (start xor (start ushr 32)) + (end xor (end ushr 32))) + (increment xor (increment ushr 32))).toInt() + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * (start xor (start ushr 32)) + (end xor (end ushr 32))) + (increment xor (increment ushr 32))).toInt() override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -138,9 +148,11 @@ public class FloatProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is FloatProgression && java.lang.Float.compare(start, other.start) == 0 && java.lang.Float.compare(end, other.end) == 0 && java.lang.Float.compare(increment, other.increment) == 0 + other is FloatProgression && (isEmpty() && other.isEmpty() || + java.lang.Float.compare(start, other.start) == 0 && java.lang.Float.compare(end, other.end) == 0 && java.lang.Float.compare(increment, other.increment) == 0) - override fun hashCode(): Int = 31 * (31 * java.lang.Float.floatToIntBits(start) + java.lang.Float.floatToIntBits(end)) + java.lang.Float.floatToIntBits(increment) + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (31 * java.lang.Float.floatToIntBits(start) + java.lang.Float.floatToIntBits(end)) + java.lang.Float.floatToIntBits(increment)) override fun toString(): String = if (increment > 0) "$start..$end step $increment" else "$start downTo $end step ${-increment}" } @@ -160,9 +172,11 @@ public class DoubleProgression( public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is DoubleProgression && java.lang.Double.compare(start, other.start) == 0 && java.lang.Double.compare(end, other.end) == 0 && java.lang.Double.compare(increment, other.increment) == 0 + other is DoubleProgression && (isEmpty() && other.isEmpty() || + java.lang.Double.compare(start, other.start) == 0 && java.lang.Double.compare(end, other.end) == 0 && java.lang.Double.compare(increment, other.increment) == 0) override fun hashCode(): Int { + if (isEmpty()) return -1 var temp = java.lang.Double.doubleToLongBits(start) var result = (temp xor (temp ushr 32)) temp = java.lang.Double.doubleToLongBits(end) diff --git a/core/builtins/src/kotlin/Ranges.kt b/core/builtins/src/kotlin/Ranges.kt index 873efbd753b..eec2e24f6c8 100644 --- a/core/builtins/src/kotlin/Ranges.kt +++ b/core/builtins/src/kotlin/Ranges.kt @@ -29,9 +29,11 @@ public class ByteRange(public override val start: Byte, public override val end: override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is ByteRange && start == other.start && end == other.end + other is ByteRange && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) - override fun hashCode(): Int = 31 * start.toInt() + end + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * start.toInt() + end) class object { public val EMPTY: ByteRange = ByteRange(1, 0) @@ -49,9 +51,11 @@ public class CharRange(public override val start: Char, public override val end: override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is CharRange && start == other.start && end == other.end + other is CharRange && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) - override fun hashCode(): Int = 31 * start.toInt() + end + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * start.toInt() + end) class object { public val EMPTY: CharRange = CharRange(1.toChar(), 0.toChar()) @@ -69,9 +73,11 @@ public class ShortRange(public override val start: Short, public override val en override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is ShortRange && start == other.start && end == other.end + other is ShortRange && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) - override fun hashCode(): Int = 31 * start.toInt() + end + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * start.toInt() + end) class object { public val EMPTY: ShortRange = ShortRange(1, 0) @@ -89,9 +95,11 @@ public class IntRange(public override val start: Int, public override val end: I override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is IntRange && start == other.start && end == other.end + other is IntRange && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) - override fun hashCode(): Int = 31 * start + end + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * start + end) class object { public val EMPTY: IntRange = IntRange(1, 0) @@ -109,9 +117,11 @@ public class LongRange(public override val start: Long, public override val end: override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is LongRange && start == other.start && end == other.end + other is LongRange && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) - override fun hashCode(): Int = (31 * (start xor (start ushr 32)) + (end xor (end ushr 32))).toInt() + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * (start xor (start ushr 32)) + (end xor (end ushr 32))).toInt() class object { public val EMPTY: LongRange = LongRange(1, 0) @@ -129,9 +139,11 @@ public class FloatRange(public override val start: Float, public override val en override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is FloatRange && java.lang.Float.compare(start, other.start) == 0 && java.lang.Float.compare(end, other.end) == 0 + other is FloatRange && (isEmpty() && other.isEmpty() || + java.lang.Float.compare(start, other.start) == 0 && java.lang.Float.compare(end, other.end) == 0) - override fun hashCode(): Int = 31 * java.lang.Float.floatToIntBits(start) + java.lang.Float.floatToIntBits(end) + override fun hashCode(): Int = + if (isEmpty()) -1 else (31 * java.lang.Float.floatToIntBits(start) + java.lang.Float.floatToIntBits(end)) class object { public val EMPTY: FloatRange = FloatRange(1.0f, 0.0f) @@ -149,9 +161,11 @@ public class DoubleRange(public override val start: Double, public override val override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is DoubleRange && java.lang.Double.compare(start, other.start) == 0 && java.lang.Double.compare(end, other.end) == 0 + other is DoubleRange && (isEmpty() && other.isEmpty() || + java.lang.Double.compare(start, other.start) == 0 && java.lang.Double.compare(end, other.end) == 0) override fun hashCode(): Int { + if (isEmpty()) return -1 var temp = java.lang.Double.doubleToLongBits(start) val result = (temp xor (temp ushr 32)) temp = java.lang.Double.doubleToLongBits(end) diff --git a/generators/src/org/jetbrains/jet/generators/builtins/progressions.kt b/generators/src/org/jetbrains/jet/generators/builtins/progressions.kt index cad6f9a603f..c55d4d7ce4a 100644 --- a/generators/src/org/jetbrains/jet/generators/builtins/progressions.kt +++ b/generators/src/org/jetbrains/jet/generators/builtins/progressions.kt @@ -45,11 +45,16 @@ class GenerateProgressions(out: PrintWriter) : BuiltInsSourceGenerator(out) { val constructor = checkNaN + checkZero val hashCode = when (kind) { - BYTE, CHAR, SHORT -> "= 31 * (31 * start.toInt() + end) + increment" - INT -> "= 31 * (31 * start + end) + increment" - LONG -> "= (31 * (31 * ${hashLong("start")} + ${hashLong("end")}) + ${hashLong("increment")}).toInt()" - FLOAT -> "= 31 * (31 * ${floatToIntBits("start")} + ${floatToIntBits("end")}) + ${floatToIntBits("increment")}" + BYTE, CHAR, SHORT -> "=\n" + + " if (isEmpty()) -1 else (31 * (31 * start.toInt() + end) + increment)" + INT -> "=\n" + + " if (isEmpty()) -1 else (31 * (31 * start + end) + increment)" + LONG -> "=\n" + + " if (isEmpty()) -1 else (31 * (31 * ${hashLong("start")} + ${hashLong("end")}) + ${hashLong("increment")}).toInt()" + FLOAT -> "=\n" + + " if (isEmpty()) -1 else (31 * (31 * ${floatToIntBits("start")} + ${floatToIntBits("end")}) + ${floatToIntBits("increment")})" DOUBLE -> "{\n" + + " if (isEmpty()) return -1\n" + " var temp = ${doubleToLongBits("start")}\n" + " var result = ${hashLong("temp")}\n" + " temp = ${doubleToLongBits("end")}\n" + @@ -74,7 +79,8 @@ class GenerateProgressions(out: PrintWriter) : BuiltInsSourceGenerator(out) { public fun isEmpty(): Boolean = if (increment > 0) start > end else start < end override fun equals(other: Any?): Boolean = - other is $progression && ${compare("start")} && ${compare("end")} && ${compare("increment")} + other is $progression && (isEmpty() && other.isEmpty() || + ${compare("start")} && ${compare("end")} && ${compare("increment")}) override fun hashCode(): Int $hashCode diff --git a/generators/src/org/jetbrains/jet/generators/builtins/ranges.kt b/generators/src/org/jetbrains/jet/generators/builtins/ranges.kt index 920540349cc..e7d92a05ed3 100644 --- a/generators/src/org/jetbrains/jet/generators/builtins/ranges.kt +++ b/generators/src/org/jetbrains/jet/generators/builtins/ranges.kt @@ -45,11 +45,16 @@ class GenerateRanges(out: PrintWriter) : BuiltInsSourceGenerator(out) { fun compare(v: String) = areEqualNumbers(kind, v) val hashCode = when (kind) { - BYTE, CHAR, SHORT -> "= 31 * start.toInt() + end" - INT -> "= 31 * start + end" - LONG -> "= (31 * ${hashLong("start")} + ${hashLong("end")}).toInt()" - FLOAT -> "= 31 * ${floatToIntBits("start")} + ${floatToIntBits("end")}" + BYTE, CHAR, SHORT -> "=\n" + + " if (isEmpty()) -1 else (31 * start.toInt() + end)" + INT -> "=\n" + + " if (isEmpty()) -1 else (31 * start + end)" + LONG -> "=\n" + + " if (isEmpty()) -1 else (31 * ${hashLong("start")} + ${hashLong("end")}).toInt()" + FLOAT -> "=\n" + + " if (isEmpty()) -1 else (31 * ${floatToIntBits("start")} + ${floatToIntBits("end")})" DOUBLE -> "{\n" + + " if (isEmpty()) return -1\n" + " var temp = ${doubleToLongBits("start")}\n" + " val result = ${hashLong("temp")}\n" + " temp = ${doubleToLongBits("end")}\n" + @@ -69,7 +74,8 @@ class GenerateRanges(out: PrintWriter) : BuiltInsSourceGenerator(out) { override fun isEmpty(): Boolean = start > end override fun equals(other: Any?): Boolean = - other is $range && ${compare("start")} && ${compare("end")} + other is $range && (isEmpty() && other.isEmpty() || + ${compare("start")} && ${compare("end")}) override fun hashCode(): Int $hashCode diff --git a/libraries/stdlib/src/kotlin/Ranges.kt b/libraries/stdlib/src/kotlin/Ranges.kt index 16cee9b9884..d4a4c46d860 100644 --- a/libraries/stdlib/src/kotlin/Ranges.kt +++ b/libraries/stdlib/src/kotlin/Ranges.kt @@ -1,12 +1,21 @@ package kotlin -public data class ComparableRange> ( +public class ComparableRange> ( public override val start: T, public override val end: T ): Range { - public override fun contains(item: T): Boolean { + override fun contains(item: T): Boolean { return start <= item && item <= end } + + override fun equals(other: Any?): Boolean { + return other is ComparableRange<*> && (isEmpty() && other.isEmpty() || + start == other.start && end == other.end) + } + + override fun hashCode(): Int { + return if (isEmpty()) -1 else 31 * start.hashCode() + end.hashCode() + } } public fun > T.rangeTo(that: T): ComparableRange { diff --git a/libraries/stdlib/test/language/RangeTest.kt b/libraries/stdlib/test/language/RangeTest.kt index 350ff2686e5..241dbf54b90 100644 --- a/libraries/stdlib/test/language/RangeTest.kt +++ b/libraries/stdlib/test/language/RangeTest.kt @@ -153,6 +153,53 @@ public class RangeTest { assertTrue(("range".."progression").isEmpty()) } + test fun emptyEquals() { + assertTrue(IntRange.EMPTY == IntRange.EMPTY) + assertEquals(IntRange.EMPTY, IntRange.EMPTY) + assertEquals(0L..42L, 0L..42L) + 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('b'..'a', 'c'..'b') + + assertTrue(1 downTo 2 == 2 downTo 3) + assertTrue(-1L downTo 0L == -2L downTo -1L) + assertEquals(-1f downTo 1f, -2f downTo 2f) + assertEquals(-4.0 downTo -3.0, -2.0 downTo -1.0) + assertEquals('j'..'a' step 4, 'u'..'q' step 2) + + assertFalse(0..1 == IntRange.EMPTY) + assertFalse(1f downTo 2f == 2f downTo 1f) + + assertEquals("range".."progression", "hashcode".."equals") + assertFalse(("aa".."bb") == ("aaa".."bbb")) + } + + test fun emptyHashCode() { + assertEquals((0..42).hashCode(), (0..42).hashCode()) + assertEquals((1.23..4.56).hashCode(), (1.23..4.56).hashCode()) + + 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((1.0 downTo 2.0).hashCode(), (2.0 downTo 3.0).hashCode()) + assertEquals(('a' downTo 'b').hashCode(), ('c' downTo 'd').hashCode()) + + assertEquals(("range".."progression").hashCode(), ("hashcode".."equals").hashCode()) + } + test fun comparableRange() { val range = "island".."isle" assertFalse("apple" in range)