diff --git a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt index fddcc6dce76..273f7e3b424 100644 --- a/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/js/src/kotlin/collections/ArrayList.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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. */ @@ -13,6 +13,10 @@ package kotlin.collections * capacity and "growth increment" concepts. */ public actual open class ArrayList internal constructor(private var array: Array) : AbstractMutableList(), MutableList, RandomAccess { + private companion object { + private val Empty = ArrayList(0).also { it.isReadOnly = true } + } + private var isReadOnly: Boolean = false /** @@ -35,7 +39,7 @@ public actual open class ArrayList internal constructor(private var array: Ar internal fun build(): List { checkIsMutable() isReadOnly = true - return this + return if (size > 0) this else Empty } /** Does nothing in this ArrayList implementation. */ diff --git a/libraries/stdlib/js/src/kotlin/collections/LinkedHashMap.kt b/libraries/stdlib/js/src/kotlin/collections/LinkedHashMap.kt index 20343a8acfc..84cafc3e1fa 100644 --- a/libraries/stdlib/js/src/kotlin/collections/LinkedHashMap.kt +++ b/libraries/stdlib/js/src/kotlin/collections/LinkedHashMap.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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. */ @@ -18,6 +18,9 @@ import kotlin.collections.MutableMap.MutableEntry * The insertion order is preserved by maintaining a doubly-linked list of all of its entries. */ public actual open class LinkedHashMap : HashMap, MutableMap { + private companion object { + private val Empty = LinkedHashMap(0).also { it.isReadOnly = true } + } /** * The entry we use includes next/prev pointers for a doubly-linked circular @@ -204,7 +207,8 @@ public actual open class LinkedHashMap : HashMap, MutableMap { internal fun build(): Map { checkIsMutable() isReadOnly = true - return this + @Suppress("UNCHECKED_CAST") + return if (size > 0) this else (Empty as Map) } actual override fun clear() { diff --git a/libraries/stdlib/js/src/kotlin/collections/LinkedHashSet.kt b/libraries/stdlib/js/src/kotlin/collections/LinkedHashSet.kt index 2f85f0b9094..8561ecbcbf8 100644 --- a/libraries/stdlib/js/src/kotlin/collections/LinkedHashSet.kt +++ b/libraries/stdlib/js/src/kotlin/collections/LinkedHashSet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 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. */ /* @@ -15,6 +15,11 @@ package kotlin.collections * This implementation preserves the insertion order of elements during the iteration. */ public actual open class LinkedHashSet : HashSet, MutableSet { + private companion object { + private val Empty = LinkedHashSet(0).also { + (it.map as LinkedHashMap).build() + } + } internal constructor(map: LinkedHashMap) : super(map) @@ -45,7 +50,7 @@ public actual open class LinkedHashSet : HashSet, MutableSet { @PublishedApi internal fun build(): Set { (map as LinkedHashMap).build() - return this + return if (size > 0) this else Empty } internal override fun checkIsMutable(): Unit = map.checkIsMutable() diff --git a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt index 22a4d9ac7e9..afa3fc0c5f4 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE file. */ @@ -13,6 +13,9 @@ actual class ArrayList private constructor( private val backingList: ArrayList?, private val root: ArrayList? ) : MutableList, RandomAccess, AbstractMutableList() { + private companion object { + private val Empty = ArrayList(0).also { it.isReadOnly = true } + } actual constructor() : this(10) @@ -28,7 +31,7 @@ actual class ArrayList private constructor( if (backingList != null) throw IllegalStateException() // just in case somebody casts subList to ArrayList checkIsMutable() isReadOnly = true - return this + return if (length > 0) this else Empty } override actual val size: Int diff --git a/libraries/stdlib/native-wasm/src/kotlin/collections/HashMap.kt b/libraries/stdlib/native-wasm/src/kotlin/collections/HashMap.kt index 7aae14ae2af..8c2584577aa 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/collections/HashMap.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/collections/HashMap.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE file. */ @@ -52,7 +52,7 @@ actual class HashMap private constructor( internal fun build(): Map { checkIsMutable() isReadOnly = true - return this + return if (size > 0) this else EmptyHolder.value() } override actual fun isEmpty(): Boolean = _size == 0 @@ -488,6 +488,15 @@ actual class HashMap private constructor( private fun computeShift(hashSize: Int): Int = hashSize.countLeadingZeroBits() + 1 } + internal object EmptyHolder { + val value_ = HashMap(0).also { it.isReadOnly = true } + + fun value(): HashMap { + @Suppress("UNCHECKED_CAST") + return value_ as HashMap + } + } + internal open class Itr( internal val map: HashMap ) { diff --git a/libraries/stdlib/native-wasm/src/kotlin/collections/HashSet.kt b/libraries/stdlib/native-wasm/src/kotlin/collections/HashSet.kt index 004bf7675fe..003873427b2 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/collections/HashSet.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/collections/HashSet.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE file. */ @@ -8,6 +8,9 @@ package kotlin.collections actual class HashSet internal constructor( private val backing: HashMap ) : MutableSet, kotlin.native.internal.KonanSet, AbstractMutableSet() { + private companion object { + private val Empty = HashSet(HashMap.EmptyHolder.value()) + } actual constructor() : this(HashMap()) @@ -23,7 +26,7 @@ actual class HashSet internal constructor( @PublishedApi internal fun build(): Set { backing.build() - return this + return if (size > 0) this else Empty } override actual val size: Int get() = backing.size diff --git a/libraries/stdlib/test/collections/ContainerBuilderTest.kt b/libraries/stdlib/test/collections/ContainerBuilderTest.kt index 46c55c90d6d..e5b3f7819f2 100644 --- a/libraries/stdlib/test/collections/ContainerBuilderTest.kt +++ b/libraries/stdlib/test/collections/ContainerBuilderTest.kt @@ -1,3 +1,8 @@ +/* + * Copyright 2010-2023 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 test.collections import test.collections.behaviors.listBehavior @@ -57,6 +62,38 @@ class ContainerBuilderTest { "entries.iterator().next().setValue(v)" to { entries.iterator().next().setValue(v) } ) + private fun emptyCollectionOperations(value: E) = listOf.() -> Unit>> ( + "add(value)" to { add(value) }, + + "addAll(listOf(value))" to { addAll(listOf(value)) }, + "addAll(emptyList())" to { addAll(emptyList()) }, + + "remove(value)" to { remove(value) }, + + "removeAll(listOf(value))" to { removeAll(listOf(value)) }, + "removeAll(emptyList())" to { removeAll(emptyList()) }, + + "retainAll(listOf(value))" to { retainAll(listOf(value)) }, + "retailAll(emptyList())" to { retainAll(emptyList()) }, + + "clear()" to { clear() } + ) + + private fun emptyListOperations(value: E) = emptyCollectionOperations(value) + listOf.() -> Unit>>( + "add(0, value)" to { add(0, value) }, + "addAll(0, listOf(value))" to { addAll(0, listOf(value)) }, + "addAll(0, emptyList())" to { addAll(0, emptyList()) }, + "listIterator().add(value)" to { listIterator().add(value) } + ) + + private fun emptyMapOperations(k: K, v: V) = listOf.() -> Unit>>( + "put(k, v)" to { put(k, v) }, + "remove(k)" to { remove(k) }, + "putAll(mapOf(k to v))" to { putAll(mapOf(k to v)) }, + "putAll(emptyMap())" to { putAll(emptyMap()) }, + "clear()" to { clear() } + ) + @Test fun buildList() { val x = buildList { @@ -91,6 +128,22 @@ class ContainerBuilderTest { } } + @Test + fun buildEmptyList() { + val empty = buildList {} + assertSame(empty, buildList {}) + assertTrue(empty is MutableList) + for ((fName, operation) in emptyListOperations(0)) { + assertFailsWith("empty.$fName") { empty.operation() } + } + + assertEquals(0, empty.size) + assertTrue(empty.isEmpty()) + assertFalse(empty.contains(42)) + assertFalse(empty.containsAll(listOf(42))) + assertTrue(empty.containsAll(emptyList())) + } + @Test fun listBuilderSubList() { buildList { @@ -150,6 +203,22 @@ class ContainerBuilderTest { } } + @Test + fun buildEmptySet() { + val empty = buildSet {} + assertSame(empty, buildSet {}) + assertTrue(empty is MutableSet) + for ((fName, operation) in emptyCollectionOperations(0)) { + assertFailsWith("empty.$fName") { empty.operation() } + } + + assertEquals(0, empty.size) + assertTrue(empty.isEmpty()) + assertFalse(empty.contains(42)) + assertFalse(empty.containsAll(listOf(42))) + assertTrue(empty.containsAll(emptyList())) + } + @Test fun buildMap() { val x = buildMap { @@ -191,4 +260,20 @@ class ContainerBuilderTest { assertFailsWith("y.entries.$fName") { y.entries.operation() } } } + + @Test + fun testBuildEmptyMap() { + val empty = buildMap {} + assertSame(empty, buildMap {}) + assertTrue(empty is MutableMap) + for ((fName, operation) in emptyMapOperations('0', 0)) { + assertFailsWith("empty.$fName") { empty.operation() } + } + + assertEquals(0, empty.size) + assertTrue(empty.isEmpty()) + assertFalse(empty.contains('0')) + assertFalse(empty.containsKey('0')) + assertFalse(empty.containsValue(0)) + } }