Skip to content

Commit d22b149

Browse files
committed
Correctly implement specialized MutableEntrySet.contains KT-41278
1 parent b3e76c9 commit d22b149

File tree

3 files changed

+98
-8
lines changed

3 files changed

+98
-8
lines changed

core/commonMain/src/implementations/immutableMap/PersistentHashMapBuilderContentViews.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@
55

66
package kotlinx.collections.immutable.implementations.immutableMap
77

8+
// intermediate abstract class to workaround KT-43321
9+
internal abstract class AbstractMapBuilderEntries<E : Map.Entry<K, V>, K, V> : AbstractMutableSet<E>() {
10+
final override fun contains(element: E): Boolean {
11+
// TODO: Eliminate this check after KT-30016 gets fixed.
12+
if ((element as? Any?) !is Map.Entry<*, *>) return false
13+
return containsEntry(element)
14+
}
15+
abstract fun containsEntry(element: Map.Entry<K, V>): Boolean
16+
}
17+
818
internal class PersistentHashMapBuilderEntries<K, V>(private val builder: PersistentHashMapBuilder<K, V>)
9-
: MutableSet<MutableMap.MutableEntry<K, V>>, AbstractMutableSet<MutableMap.MutableEntry<K, V>>() {
19+
: AbstractMapBuilderEntries<MutableMap.MutableEntry<K, V>, K, V>() {
1020
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
1121
throw UnsupportedOperationException()
1222
}
@@ -28,9 +38,7 @@ internal class PersistentHashMapBuilderEntries<K, V>(private val builder: Persis
2838
override val size: Int
2939
get() = builder.size
3040

31-
override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean {
32-
// TODO: Eliminate this check after KT-30016 gets fixed.
33-
if ((element as Any?) !is Map.Entry<*, *>) return false
41+
override fun containsEntry(element: Map.Entry<K, V>): Boolean {
3442
return builder[element.key]?.let { candidate -> candidate == element.value }
3543
?: (element.value == null && builder.containsKey(element.key))
3644
}

core/commonMain/src/implementations/persistentOrderedMap/PersistentOrderedMapBuilderContentViews.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
package kotlinx.collections.immutable.implementations.persistentOrderedMap
77

8+
import kotlinx.collections.immutable.implementations.immutableMap.AbstractMapBuilderEntries
9+
810
internal class PersistentOrderedMapBuilderEntries<K, V>(private val builder: PersistentOrderedMapBuilder<K, V>)
9-
: MutableSet<MutableMap.MutableEntry<K, V>>, AbstractMutableSet<MutableMap.MutableEntry<K, V>>() {
11+
: AbstractMapBuilderEntries<MutableMap.MutableEntry<K, V>, K, V>() {
1012
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean {
1113
throw UnsupportedOperationException()
1214
}
@@ -28,9 +30,7 @@ internal class PersistentOrderedMapBuilderEntries<K, V>(private val builder: Per
2830
override val size: Int
2931
get() = builder.size
3032

31-
override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean {
32-
// TODO: Eliminate this check after KT-30016 gets fixed.
33-
if ((element as Any?) !is Map.Entry<*, *>) return false
33+
override fun containsEntry(element: Map.Entry<K, V>): Boolean {
3434
return builder[element.key]?.let { candidate -> candidate == element.value }
3535
?: (element.value == null && builder.containsKey(element.key))
3636
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2016-2021 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package tests.contract.map
7+
8+
import kotlinx.collections.immutable.persistentHashMapOf
9+
import kotlinx.collections.immutable.persistentMapOf
10+
import kotlin.test.Test
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFalse
13+
import kotlin.test.assertTrue
14+
15+
class KT41278Test {
16+
// Based on https://youtrack.jetbrains.com/issue/KT-42428.
17+
private fun doTest(map: Map<String, Int>, key: String, value: Int, createEntry: (String, Int) -> Map.Entry<String, Int>) {
18+
assertTrue(map.keys.contains(key))
19+
assertEquals(value, map[key])
20+
// This one requires special efforts to make it work this way.
21+
// map.entries can in fact be `MutableSet<MutableMap.MutableEntry>`,
22+
// which [contains] method takes [MutableEntry], so the compiler may generate special bridge
23+
// returning false for values that aren't [MutableEntry].
24+
assertTrue(map.entries.contains(createEntry(key, value)))
25+
assertTrue(map.entries.toSet().contains(createEntry(key, value)))
26+
27+
assertFalse(map.entries.contains(null as Any?))
28+
assertFalse(map.entries.contains("not an entry" as Any?))
29+
}
30+
31+
@Test
32+
fun persistentOrderedMap() {
33+
val mapLetterToIndex = ('a'..'z').mapIndexed { i, c -> "$c" to i }.fold(persistentMapOf<String, Int>()) { map, pair ->
34+
map.put(pair.first, pair.second)
35+
}
36+
37+
doTest(mapLetterToIndex, "h", 7, ::TestMapEntry)
38+
doTest(mapLetterToIndex, "h", 7, ::TestMutableMapEntry)
39+
}
40+
41+
@Test
42+
fun persistentHashMap() {
43+
val mapLetterToIndex = ('a'..'z').mapIndexed { i, c -> "$c" to i }.fold(persistentHashMapOf<String, Int>()) { map, pair ->
44+
map.put(pair.first, pair.second)
45+
}
46+
47+
doTest(mapLetterToIndex, "h", 7, ::TestMapEntry)
48+
doTest(mapLetterToIndex, "h", 7, ::TestMutableMapEntry)
49+
}
50+
51+
@Test
52+
fun persistentOrderedMapBuilder() {
53+
val mapLetterToIndex = persistentMapOf<String, Int>().builder().apply { putAll(('a'..'z').mapIndexed { i, c -> "$c" to i }) }
54+
55+
doTest(mapLetterToIndex, "h", 7, ::TestMapEntry)
56+
doTest(mapLetterToIndex, "h", 7, ::TestMutableMapEntry)
57+
}
58+
59+
@Test
60+
fun persistentHashMapBuilder() {
61+
val mapLetterToIndex = persistentHashMapOf<String, Int>().builder().apply { putAll(('a'..'z').mapIndexed { i, c -> "$c" to i }) }
62+
63+
doTest(mapLetterToIndex, "h", 7, ::TestMapEntry)
64+
doTest(mapLetterToIndex, "h", 7, ::TestMutableMapEntry)
65+
}
66+
}
67+
68+
private class TestMapEntry<out K, out V>(override val key: K, override val value: V) : Map.Entry<K, V> {
69+
override fun toString(): String = "$key=$value"
70+
override fun hashCode(): Int = key.hashCode() xor value.hashCode()
71+
override fun equals(other: Any?): Boolean =
72+
other is Map.Entry<*, *> && key == other.key && value == other.value
73+
}
74+
75+
private class TestMutableMapEntry<K, V>(override val key: K, override val value: V) : MutableMap.MutableEntry<K, V> {
76+
override fun toString(): String = "$key=$value"
77+
override fun hashCode(): Int = key.hashCode() xor value.hashCode()
78+
override fun equals(other: Any?): Boolean =
79+
other is Map.Entry<*, *> && key == other.key && value == other.value
80+
81+
override fun setValue(newValue: V): V = TODO("Not yet implemented")
82+
}

0 commit comments

Comments
 (0)