diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/collections/Collections.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/collections/Collections.kt index 3eb094f28da..4072e374f3e 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/collections/Collections.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/collections/Collections.kt @@ -135,3 +135,5 @@ internal actual inline fun checkCountOverflow(count: Int): Int { } return count } + +internal actual fun brittleContainsOptimizationEnabled(): Boolean = false diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index 25c86f27a83..f32e2a68271 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -2995,7 +2995,7 @@ public operator fun Iterable.minus(element: T): List { */ public operator fun Iterable.minus(elements: Array): List { if (elements.isEmpty()) return this.toList() - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() return this.filterNot { it in other } } @@ -3019,7 +3019,7 @@ public operator fun Iterable.minus(elements: Iterable): List { * a correct and stable implementation of `hashCode()` that doesn't change between successive invocations. */ public operator fun Iterable.minus(elements: Sequence): List { - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() if (other.isEmpty()) return this.toList() return this.filterNot { it in other } diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 2b395a3e894..666ba223655 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -2449,7 +2449,7 @@ public operator fun Sequence.minus(elements: Array): Sequence { if (elements.isEmpty()) return this return object: Sequence { override fun iterator(): Iterator { - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() return this@minus.filterNot { it in other }.iterator() } } @@ -2492,7 +2492,7 @@ public operator fun Sequence.minus(elements: Iterable): Sequence { public operator fun Sequence.minus(elements: Sequence): Sequence { return object: Sequence { override fun iterator(): Iterator { - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() if (other.isEmpty()) return this@minus.iterator() else diff --git a/libraries/stdlib/js/src/kotlin/collections.kt b/libraries/stdlib/js/src/kotlin/collections.kt index b6eeeb8cb86..bdd9c085c92 100644 --- a/libraries/stdlib/js/src/kotlin/collections.kt +++ b/libraries/stdlib/js/src/kotlin/collections.kt @@ -265,4 +265,6 @@ internal actual fun mapCapacity(expectedSize: Int) = expectedSize @PublishedApi internal fun checkBuilderCapacity(capacity: Int) { require(capacity >= 0) { "capacity must be non-negative." } -} \ No newline at end of file +} + +internal actual fun brittleContainsOptimizationEnabled(): Boolean = false \ No newline at end of file diff --git a/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt b/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt index 90fa8721a9b..899d37fca2e 100644 --- a/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/collections/CollectionsJVM.kt @@ -123,3 +123,5 @@ internal actual inline fun checkCountOverflow(count: Int): Int { return count } + +internal actual fun brittleContainsOptimizationEnabled(): Boolean = System.getProperty("kotlin.collections.removing.legacymode").toBoolean() diff --git a/libraries/stdlib/src/kotlin/collections/BrittleContainsOptimization.kt b/libraries/stdlib/src/kotlin/collections/BrittleContainsOptimization.kt new file mode 100644 index 00000000000..12af4aedcdf --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/BrittleContainsOptimization.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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 + + +/** Returns true if the brittle contains optimization is enabled. See KT-45438. */ +internal expect fun brittleContainsOptimizationEnabled(): Boolean + +/** + * Returns true if [brittleContainsOptimizationEnabled] is true + * and it's safe to convert this collection to a set without changing contains method behavior. + */ +private fun Collection.safeToConvertToSet() = brittleContainsOptimizationEnabled() && size > 2 && this is ArrayList + +/** + * When [brittleContainsOptimizationEnabled] is true: + * - Converts this [Iterable] to a set if it is not a [Collection]. + * - Converts this [Collection] to a set, when it's worth so and it doesn't change contains method behavior. + * - Otherwise returns this. + * When [brittleContainsOptimizationEnabled] is false: + * - Converts this [Iterable] to a list if it is not a [Collection]. + * - Otherwise returns this. + */ +internal fun Iterable.convertToSetForSetOperationWith(source: Iterable): Collection = + when (this) { + is Set -> this + is Collection -> + when { + source is Collection && source.size < 2 -> this + else -> if (this.safeToConvertToSet()) toHashSet() else this + } + else -> if (brittleContainsOptimizationEnabled()) toHashSet() else toList() + } + +/** + * When [brittleContainsOptimizationEnabled] is true: + * - Converts this [Iterable] to a set if it is not a [Collection]. + * - Converts this [Collection] to a set, when it's worth so and it doesn't change contains method behavior. + * - Otherwise returns this. + * When [brittleContainsOptimizationEnabled] is false: + * - Converts this [Iterable] to a list if it is not a [Collection]. + * - Otherwise returns this. + */ +internal fun Iterable.convertToSetForSetOperation(): Collection = + when (this) { + is Set -> this + is Collection -> if (this.safeToConvertToSet()) toHashSet() else this + else -> if (brittleContainsOptimizationEnabled()) toHashSet() else toList() + } + +/** + * Converts this sequence to a set if [brittleContainsOptimizationEnabled] is true, + * otherwise converts it to a list. + */ +internal fun Sequence.convertToSetForSetOperation(): Collection = + if (brittleContainsOptimizationEnabled()) toHashSet() else toList() + +/** + * Converts this array to a set if [brittleContainsOptimizationEnabled] is true, + * otherwise converts it to a list. + */ +internal fun Array.convertToSetForSetOperation(): Collection = + if (brittleContainsOptimizationEnabled()) toHashSet() else asList() \ No newline at end of file diff --git a/libraries/stdlib/src/kotlin/collections/Iterables.kt b/libraries/stdlib/src/kotlin/collections/Iterables.kt index d2b1b531361..404353a75ef 100644 --- a/libraries/stdlib/src/kotlin/collections/Iterables.kt +++ b/libraries/stdlib/src/kotlin/collections/Iterables.kt @@ -38,29 +38,6 @@ internal fun Iterable.collectionSizeOrNull(): Int? = if (this is Collecti @PublishedApi internal fun Iterable.collectionSizeOrDefault(default: Int): Int = if (this is Collection<*>) this.size else default -/** Returns true when it's safe to convert this collection to a set without changing contains method behavior. */ -private fun Collection.safeToConvertToSet() = size > 2 && this is ArrayList - -/** Converts this collection to a set, when it's worth so and it doesn't change contains method behavior. */ -internal fun Iterable.convertToSetForSetOperationWith(source: Iterable): Collection = - when (this) { - is Set -> this - is Collection -> - when { - source is Collection && source.size < 2 -> this - else -> if (this.safeToConvertToSet()) toHashSet() else this - } - else -> toHashSet() - } - -/** Converts this collection to a set, when it's worth so and it doesn't change contains method behavior. */ -internal fun Iterable.convertToSetForSetOperation(): Collection = - when (this) { - is Set -> this - is Collection -> if (this.safeToConvertToSet()) toHashSet() else this - else -> toHashSet() - } - /** * Returns a single list of all elements from all collections in the given collection. diff --git a/libraries/stdlib/src/kotlin/collections/MutableCollections.kt b/libraries/stdlib/src/kotlin/collections/MutableCollections.kt index d8348557901..6dbda52670a 100644 --- a/libraries/stdlib/src/kotlin/collections/MutableCollections.kt +++ b/libraries/stdlib/src/kotlin/collections/MutableCollections.kt @@ -152,7 +152,7 @@ public fun MutableCollection.removeAll(elements: Iterable): Boolean * Removes all elements from this [MutableCollection] that are also contained in the given [elements] sequence. */ public fun MutableCollection.removeAll(elements: Sequence): Boolean { - val set = elements.toHashSet() + val set = elements.convertToSetForSetOperation() return set.isNotEmpty() && removeAll(set) } @@ -160,7 +160,7 @@ public fun MutableCollection.removeAll(elements: Sequence): Boolean * Removes all elements from this [MutableCollection] that are also contained in the given [elements] array. */ public fun MutableCollection.removeAll(elements: Array): Boolean { - return elements.isNotEmpty() && removeAll(elements.toHashSet()) + return elements.isNotEmpty() && removeAll(elements.convertToSetForSetOperation()) } /** @@ -175,7 +175,7 @@ public fun MutableCollection.retainAll(elements: Iterable): Boolean */ public fun MutableCollection.retainAll(elements: Array): Boolean { if (elements.isNotEmpty()) - return retainAll(elements.toHashSet()) + return retainAll(elements.convertToSetForSetOperation()) else return retainNothing() } @@ -184,7 +184,7 @@ public fun MutableCollection.retainAll(elements: Array): Boolea * Retains only elements of this [MutableCollection] that are contained in the given [elements] sequence. */ public fun MutableCollection.retainAll(elements: Sequence): Boolean { - val set = elements.toHashSet() + val set = elements.convertToSetForSetOperation() if (set.isNotEmpty()) return retainAll(set) else diff --git a/libraries/stdlib/wasm/src/kotlin/collections/Collections.kt b/libraries/stdlib/wasm/src/kotlin/collections/Collections.kt index 87abd933c9f..cfcd91eb826 100644 --- a/libraries/stdlib/wasm/src/kotlin/collections/Collections.kt +++ b/libraries/stdlib/wasm/src/kotlin/collections/Collections.kt @@ -108,4 +108,6 @@ internal actual inline fun buildMapInternal(builderAction: MutableMap buildMapInternal(capacity: Int, builderAction: MutableMap.() -> Unit): Map { return TODO("Wasm stdlib: Collections") -} \ No newline at end of file +} + +internal actual fun brittleContainsOptimizationEnabled(): Boolean = false \ No newline at end of file diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt index 7287b41a336..75dcda2ff5a 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt @@ -455,7 +455,7 @@ object Generators : TemplateGroupBase() { body { """ if (elements.isEmpty()) return this.toList() - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() return this.filterNot { it in other } """ } @@ -491,7 +491,7 @@ object Generators : TemplateGroupBase() { if (elements.isEmpty()) return this return object: Sequence { override fun iterator(): Iterator { - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() return this@minus.filterNot { it in other }.iterator() } } @@ -513,7 +513,7 @@ object Generators : TemplateGroupBase() { specialFor(Sets, Sequences) { returns("SELF") } body { """ - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() if (other.isEmpty()) return this.toList() @@ -552,7 +552,7 @@ object Generators : TemplateGroupBase() { """ return object: Sequence { override fun iterator(): Iterator { - val other = elements.toHashSet() + val other = elements.convertToSetForSetOperation() if (other.isEmpty()) return this@minus.iterator() else