diff --git a/libraries/stdlib/js/src/js/arrayUtils.js b/libraries/stdlib/js/src/js/arrayUtils.js index 3a0ca02be80..37c16911bf6 100644 --- a/libraries/stdlib/js/src/js/arrayUtils.js +++ b/libraries/stdlib/js/src/js/arrayUtils.js @@ -48,20 +48,8 @@ Kotlin.arrayToString = function (a) { return "[" + Array.prototype.map.call(a, function(e) { return toString(e); }).join(", ") + "]"; }; -Kotlin.arrayDeepToString = function (a, visited) { - visited = visited || [a]; - var toString = Kotlin.isCharArray(a) ? String.fromCharCode : Kotlin.toString; - return "[" + Array.prototype.map.call(a, function (e) { - if (Kotlin.isArrayish(e) && visited.indexOf(e) < 0) { - visited.push(e); - var result = Kotlin.arrayDeepToString(e, visited); - visited.pop(); - return result; - } - else { - return toString(e); - } - }).join(", ") + "]"; +Kotlin.arrayDeepToString = function (arr) { + return Kotlin.kotlin.collections.contentDeepToStringImpl(arr); }; Kotlin.arrayEquals = function (a, b) { @@ -81,24 +69,7 @@ Kotlin.arrayEquals = function (a, b) { }; Kotlin.arrayDeepEquals = function (a, b) { - if (a === b) { - return true; - } - if (!Kotlin.isArrayish(b) || a.length !== b.length) { - return false; - } - - for (var i = 0, n = a.length; i < n; i++) { - if (Kotlin.isArrayish(a[i])) { - if (!Kotlin.arrayDeepEquals(a[i], b[i])) { - return false; - } - } - else if (!Kotlin.equals(a[i], b[i])) { - return false; - } - } - return true; + return Kotlin.kotlin.collections.contentDeepEqualsImpl(a, b); }; Kotlin.arrayHashCode = function (arr) { @@ -110,12 +81,7 @@ Kotlin.arrayHashCode = function (arr) { }; Kotlin.arrayDeepHashCode = function (arr) { - var result = 1; - for (var i = 0, n = arr.length; i < n; i++) { - var e = arr[i]; - result = ((31 * result | 0) + (Kotlin.isArrayish(e) ? Kotlin.arrayDeepHashCode(e) : Kotlin.hashCode(e))) | 0; - } - return result; + return Kotlin.kotlin.collections.contentDeepHashCodeImpl(arr); }; Kotlin.primitiveArraySort = function (array) { diff --git a/libraries/stdlib/js/src/kotlin/collections/ArraysJs.kt b/libraries/stdlib/js/src/kotlin/collections/ArraysJs.kt new file mode 100644 index 00000000000..a8bf9ae2c8e --- /dev/null +++ b/libraries/stdlib/js/src/kotlin/collections/ArraysJs.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package kotlin.collections + + +@UseExperimental(ExperimentalUnsignedTypes::class) +@SinceKotlin("1.3") +@kotlin.js.JsName("contentDeepHashCodeImpl") +internal fun Array.contentDeepHashCodeImpl(): Int { + var result = 1 + for (element in this) { + val elementHash = when { + element == null -> 0 + js("Kotlin").isArrayish(element) -> (element.unsafeCast>()).contentDeepHashCodeImpl() + + element is UByteArray -> element.contentHashCode() + element is UShortArray -> element.contentHashCode() + element is UIntArray -> element.contentHashCode() + element is ULongArray -> element.contentHashCode() + + else -> element.hashCode() + } + + result = 31 * result + elementHash + } + return result +} \ No newline at end of file diff --git a/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt b/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt index ec2f042bf30..c42b738e35b 100644 --- a/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt +++ b/libraries/stdlib/jvm/src/generated/_ArraysJvm.kt @@ -295,9 +295,13 @@ public fun CharArray.binarySearch(element: Char, fromIndex: Int = 0, toIndex: In * If any of arrays contains itself on any nesting level the behavior is undefined. */ @SinceKotlin("1.1") +@JvmName("contentDeepEqualsInline") @kotlin.internal.InlineOnly public actual inline infix fun Array.contentDeepEquals(other: Array): Boolean { - return java.util.Arrays.deepEquals(this, other) + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepEqualsImpl(other) + else + return java.util.Arrays.deepEquals(this, other) } /** @@ -307,9 +311,13 @@ public actual inline infix fun Array.contentDeepEquals(other: Array Array.contentDeepHashCode(): Int { - return java.util.Arrays.deepHashCode(this) + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepHashCodeImpl() + else + return java.util.Arrays.deepHashCode(this) } /** @@ -322,9 +330,13 @@ public actual inline fun Array.contentDeepHashCode(): Int { * @sample samples.collections.Arrays.ContentOperations.contentDeepToString */ @SinceKotlin("1.1") +@JvmName("contentDeepToStringInline") @kotlin.internal.InlineOnly public actual inline fun Array.contentDeepToString(): String { - return java.util.Arrays.deepToString(this) + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepToStringImpl() + else + return java.util.Arrays.deepToString(this) } /** diff --git a/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt b/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt index f74ed16b78a..7fbbf7383c4 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/ArraysJVM.kt @@ -47,4 +47,14 @@ internal actual fun arrayOfNulls(reference: Array, size: Int): Array { @SinceKotlin("1.3") internal fun copyOfRangeToIndexCheck(toIndex: Int, size: Int) { if (toIndex > size) throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).") -} \ No newline at end of file +} + + +@SinceKotlin("1.3") +@PublishedApi +@kotlin.jvm.JvmName("contentDeepHashCode") +internal fun Array.contentDeepHashCodeImpl(): Int = +// returns valid result for unsigned arrays by accident: +// hash code of an inline class, which an unsigned array is, +// is calculated structurally as in a data class + java.util.Arrays.deepHashCode(this) diff --git a/libraries/stdlib/src/kotlin/collections/Arrays.kt b/libraries/stdlib/src/kotlin/collections/Arrays.kt index caaf80e0b4d..77a6e96ea79 100644 --- a/libraries/stdlib/src/kotlin/collections/Arrays.kt +++ b/libraries/stdlib/src/kotlin/collections/Arrays.kt @@ -65,3 +65,95 @@ public inline fun Array<*>?.isNullOrEmpty(): Boolean { @Suppress("UPPER_BOUND_CANNOT_BE_ARRAY") public inline fun C.ifEmpty(defaultValue: () -> R): R where C : Array<*>, C : R = if (isEmpty()) defaultValue() else this + + +@SinceKotlin("1.3") +@PublishedApi +@kotlin.jvm.JvmName("contentDeepEquals") +@kotlin.js.JsName("contentDeepEqualsImpl") +internal fun Array.contentDeepEqualsImpl(other: Array): Boolean { + if (this === other) return true + if (this.size != other.size) return false + + for (i in indices) { + val v1 = this[i] + val v2 = other[i] + + if (v1 === v2) { + continue + } else if (v1 == null || v2 == null) { + return false + } + + when { + v1 is Array<*> && v2 is Array<*> -> if (!v1.contentDeepEquals(v2)) return false + v1 is ByteArray && v2 is ByteArray -> if (!v1.contentEquals(v2)) return false + v1 is ShortArray && v2 is ShortArray -> if (!v1.contentEquals(v2)) return false + v1 is IntArray && v2 is IntArray -> if (!v1.contentEquals(v2)) return false + v1 is LongArray && v2 is LongArray -> if (!v1.contentEquals(v2)) return false + v1 is FloatArray && v2 is FloatArray -> if (!v1.contentEquals(v2)) return false + v1 is DoubleArray && v2 is DoubleArray -> if (!v1.contentEquals(v2)) return false + v1 is CharArray && v2 is CharArray -> if (!v1.contentEquals(v2)) return false + v1 is BooleanArray && v2 is BooleanArray -> if (!v1.contentEquals(v2)) return false + + v1 is UByteArray && v2 is UByteArray -> if (!v1.contentEquals(v2)) return false + v1 is UShortArray && v2 is UShortArray -> if (!v1.contentEquals(v2)) return false + v1 is UIntArray && v2 is UIntArray -> if (!v1.contentEquals(v2)) return false + v1 is ULongArray && v2 is ULongArray -> if (!v1.contentEquals(v2)) return false + + else -> if (v1 != v2) return false + } + + } + return true +} + +@SinceKotlin("1.3") +@PublishedApi +@kotlin.jvm.JvmName("contentDeepToString") +@kotlin.js.JsName("contentDeepToStringImpl") +internal fun Array.contentDeepToStringImpl(): String { + val length = size.coerceAtMost((Int.MAX_VALUE - 2) / 5) * 5 + 2 // in order not to overflow Int.MAX_VALUE + return buildString(length) { + contentDeepToStringInternal(this, mutableSetOf()) + } +} + +@UseExperimental(ExperimentalUnsignedTypes::class) +private fun Array.contentDeepToStringInternal(result: StringBuilder, processed: MutableSet>) { + if (this in processed) { + result.append("[...]") + return + } + processed.add(this) + result.append('[') + + for (i in indices) { + if (i != 0) { + result.append(", ") + } + val element = this[i] + when (element) { + null -> result.append("null") + is Array<*> -> element.contentDeepToStringInternal(result, processed) + is ByteArray -> result.append(element.contentToString()) + is ShortArray -> result.append(element.contentToString()) + is IntArray -> result.append(element.contentToString()) + is LongArray -> result.append(element.contentToString()) + is FloatArray -> result.append(element.contentToString()) + is DoubleArray -> result.append(element.contentToString()) + is CharArray -> result.append(element.contentToString()) + is BooleanArray -> result.append(element.contentToString()) + + is UByteArray -> result.append(element.contentToString()) + is UShortArray -> result.append(element.contentToString()) + is UIntArray -> result.append(element.contentToString()) + is ULongArray -> result.append(element.contentToString()) + + else -> result.append(element.toString()) + } + } + + result.append(']') + processed.remove(this) +} \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArraysTest.kt b/libraries/stdlib/test/collections/ArraysTest.kt index c40bdd29245..a1940563a95 100644 --- a/libraries/stdlib/test/collections/ArraysTest.kt +++ b/libraries/stdlib/test/collections/ArraysTest.kt @@ -209,10 +209,19 @@ class ArraysTest { @Test fun contentDeepEquals() { val arr1 = arrayOf("a", 1, intArrayOf(2)) val arr2 = arrayOf("a", 1, intArrayOf(2)) + val arr3 = arrayOf("a", 1, uintArrayOf(2u)) + val arr4 = arrayOf("a", 1, uintArrayOf(2u)) assertFalse(arr1 contentEquals arr2) assertTrue(arr1 contentDeepEquals arr2) + + assertFalse(arr1 contentDeepEquals arr3) + assertTrue(arr3 contentDeepEquals arr4) + arr2[2] = arr1 assertFalse(arr1 contentDeepEquals arr2) + + arr4[2] = arr3 + assertFalse(arr3 contentDeepEquals arr4) } @Test fun contentToString() { @@ -230,8 +239,13 @@ class ArraysTest { return } - val arr = arrayOf("aa", 1, null, charArrayOf('d')) - assertEquals("[aa, 1, null, [d]]", arr.contentDeepToString()) + val arr = arrayOf( + "aa", 1, null, arrayOf(arrayOf("foo")), charArrayOf('d'), booleanArrayOf(false), + intArrayOf(-1), longArrayOf(-1), shortArrayOf(-1), byteArrayOf(-1), + uintArrayOf(UInt.MAX_VALUE), ulongArrayOf(ULong.MAX_VALUE), ushortArrayOf(UShort.MAX_VALUE), ubyteArrayOf(UByte.MAX_VALUE), + doubleArrayOf(3.14), floatArrayOf(1.25f) + ) + assertEquals("[aa, 1, null, [[foo]], [d], [false], [-1], [-1], [-1], [-1], [4294967295], [18446744073709551615], [65535], [255], [3.14], [1.25]]", arr.contentDeepToString()) } @Test fun contentDeepToStringNoRecursion() { @@ -262,6 +276,16 @@ class ArraysTest { @Test fun contentDeepHashCode() { val arr = arrayOf(null, Value(2), arrayOf(Value(3))) assertEquals(((1*31 + 0)*31 + 2) * 31 + (1 * 31 + 3), arr.contentDeepHashCode()) + + val intArray2 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4)) + val intList2 = listOf(listOf(1, 2), listOf(3, 4)) + + assertEquals(intList2.hashCode(), intArray2.contentDeepHashCode()) + + val uintArray2 = arrayOf(uintArrayOf(1u, 2u), uintArrayOf(3u, 4u)) + val uintList2 = listOf(listOf(1u, 2u), listOf(3u, 4u)) + + assertEquals(uintList2.hashCode(), uintArray2.contentDeepHashCode()) } 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 4cdcc6aa49e..518abb629ce 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 @@ -993,6 +993,9 @@ public final class kotlin/collections/ArraysKt { public static final fun contains ([Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun contains ([SS)Z public static final fun contains ([ZZ)Z + public static final fun contentDeepEquals ([Ljava/lang/Object;[Ljava/lang/Object;)Z + public static final fun contentDeepHashCode ([Ljava/lang/Object;)I + public static final fun contentDeepToString ([Ljava/lang/Object;)Ljava/lang/String; public static final fun copyInto ([B[BIII)[B public static final fun copyInto ([C[CIII)[C public static final fun copyInto ([D[DIII)[D diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt index d03517e088c..22bf23ce456 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt @@ -105,7 +105,15 @@ object ArrayOps : TemplateGroupBase() { returns("Boolean") on(Platform.JVM) { inlineOnly() - body { "return java.util.Arrays.deepEquals(this, other)" } + annotation("""@JvmName("contentDeepEqualsInline")""") + body { + """ + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepEqualsImpl(other) + else + return java.util.Arrays.deepEquals(this, other) + """ + } } on(Platform.JS) { annotation("""@library("arrayDeepEquals")""") @@ -155,7 +163,15 @@ object ArrayOps : TemplateGroupBase() { returns("String") on(Platform.JVM) { inlineOnly() - body { "return java.util.Arrays.deepToString(this)" } + annotation("""@JvmName("contentDeepToStringInline")""") + body { + """ + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepToStringImpl() + else + return java.util.Arrays.deepToString(this) + """ + } } on(Platform.JS) { annotation("""@library("arrayDeepToString")""") @@ -200,7 +216,15 @@ object ArrayOps : TemplateGroupBase() { returns("Int") on(Platform.JVM) { inlineOnly() - body { "return java.util.Arrays.deepHashCode(this)" } + annotation("""@JvmName("contentDeepHashCodeInline")""") + body { + """ + if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0)) + return contentDeepHashCodeImpl() + else + return java.util.Arrays.deepHashCode(this) + """ + } } on(Platform.JS) { annotation("""@library("arrayDeepHashCode")""")