Fix MutableMap.entries.contains(Map.Entry)

#KT-42428 Fixed.
This commit is contained in:
SvyatoslavScherbina
2020-11-09 10:50:22 +03:00
committed by Stanislav Erokhin
parent 1bd8eb62fb
commit 3e92699962
3 changed files with 91 additions and 11 deletions
@@ -4837,6 +4837,7 @@ task override_konan_properties0(type: KonanDriverTest) {
KotlinNativeTestKt.createTest(project, 'stdlibTest', KonanGTest) { task ->
def sources = UtilsKt.getFilesToCompile(project,
[ 'build/stdlib_external/stdlib', 'stdlib_external/utils.kt',
'stdlib_external/collections',
'stdlib_external/jsCollectionFactoriesActuals.kt',
'stdlib_external/text/StringEncodingTestNative.kt'],
[ 'build/stdlib_external/stdlib/test/internalAnnotations.kt' ])
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2020 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 kotlin.test.*
// TODO: consider moving to common stdlib tests.
class KT42428Test {
private val listOfLetterIndexPairs = ('a'..'z').withIndex().map { (i, c) -> "$c" to i }
private val mapOfLetterToIndex = listOfLetterIndexPairs.toMap()
@Test fun testListOfPairsToMapEntriesContainsMapEntry() {
testMapEntriesContainsMapEntry(listOfLetterIndexPairs.toMap(), "h", 7)
}
@Test fun testMapToMutableMapEntriesContainsMapEntry() {
testMapEntriesContainsMapEntry(mapOfLetterToIndex.toMutableMap(), "h", 7)
}
@Test fun testHashMapEntriesContainsMapEntry() {
testMapEntriesContainsMapEntry(HashMap(mapOfLetterToIndex), "h", 7)
}
@Test fun testLinkedHashMapEntriesContainsMapEntry() {
testMapEntriesContainsMapEntry(LinkedHashMap(mapOfLetterToIndex), "h", 7)
}
// Based on https://youtrack.jetbrains.com/issue/KT-42428.
private fun testMapEntriesContainsMapEntry(map: Map<String, Int>, key: String, value: Int) {
data class SimpleEntry<out K, out V>(override val key: K, override val value: V) : Map.Entry<K, V> {
override fun equals(other: Any?): Boolean =
other is Map.Entry<*, *> && key == other.key && value == other.value
override fun hashCode(): Int = key.hashCode() xor value.hashCode()
override fun toString(): String = "$key=$value"
}
assertTrue(map.keys.contains(key))
// This one requires special efforts to make it work this way.
// map.entries can in fact be `MutableSet<MutableMap.MutableEntry>`,
// which [contains] method takes [MutableEntry], so the compiler may generate special bridge
// returning false for values that aren't [MutableEntry] (including [SimpleEntry]).
assertTrue(map.entries.contains(SimpleEntry(key, value)))
assertTrue(map.entries.toSet().contains(SimpleEntry(key, value)))
}
}
@@ -630,31 +630,54 @@ internal class HashMapValues<V> internal constructor(
}
}
internal class HashMapEntrySet<K, V> internal constructor(
/**
* Note: intermediate class with [E] `: Map.Entry<K, V>` is required to support
* [contains] for values that are [Map.Entry] but not [MutableMap.MutableEntry],
* and probably same for other functions.
* This is important because an instance of this class can be used as a result of [Map.entries],
* which should support [contains] for [Map.Entry].
* For example, this happens when upcasting [MutableMap] to [Map].
*
* The compiler enables special type-safe barriers to methods like [contains], which has [UnsafeVariance].
* Changing type from [MutableMap.MutableEntry] to [E] makes the compiler generate barriers checking that
* argument `is` [E] (so technically `is` [Map.Entry]) instead of `is` [MutableMap.MutableEntry].
*
* See also [KT-42248](https://youtrack.jetbrains.com/issue/KT-42428).
*/
internal abstract class HashMapEntrySetBase<K, V, E : Map.Entry<K, V>> internal constructor(
val backing: HashMap<K, V>
) : MutableSet<MutableMap.MutableEntry<K, V>>, kotlin.native.internal.KonanSet<MutableMap.MutableEntry<K, V>>, AbstractMutableSet<MutableMap.MutableEntry<K, V>>() {
) : MutableSet<E>, kotlin.native.internal.KonanSet<E>, AbstractMutableSet<E>() {
override val size: Int get() = backing.size
override fun isEmpty(): Boolean = backing.isEmpty()
override fun contains(element: MutableMap.MutableEntry<K, V>): Boolean = backing.containsEntry(element)
override fun getElement(element: MutableMap.MutableEntry<K, V>): MutableMap.MutableEntry<K, V>? = backing.getEntry(element)
override fun contains(element: E): Boolean = backing.containsEntry(element)
override fun getElement(element: E): E? = getEntry(element)
protected abstract fun getEntry(element: Map.Entry<K, V>): E?
override fun clear() = backing.clear()
override fun add(element: MutableMap.MutableEntry<K, V>): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean = throw UnsupportedOperationException()
override fun remove(element: MutableMap.MutableEntry<K, V>): Boolean = backing.removeEntry(element)
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> = backing.entriesIterator()
override fun containsAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean = backing.containsAllEntries(elements)
override fun add(element: E): Boolean = throw UnsupportedOperationException()
override fun addAll(elements: Collection<E>): Boolean = throw UnsupportedOperationException()
override fun remove(element: E): Boolean = backing.removeEntry(element)
override fun containsAll(elements: Collection<E>): Boolean = backing.containsAllEntries(elements)
override fun removeAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
override fun removeAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.removeAll(elements)
}
override fun retainAll(elements: Collection<MutableMap.MutableEntry<K, V>>): Boolean {
override fun retainAll(elements: Collection<E>): Boolean {
backing.checkIsMutable()
return super.retainAll(elements)
}
}
internal class HashMapEntrySet<K, V> internal constructor(
backing: HashMap<K, V>
) : HashMapEntrySetBase<K, V, MutableMap.MutableEntry<K, V>>(backing) {
override fun getEntry(element: Map.Entry<K, V>): MutableMap.MutableEntry<K, V>? = backing.getEntry(element)
override fun iterator(): MutableIterator<MutableMap.MutableEntry<K, V>> = backing.entriesIterator()
}
// This hash map keeps insertion order.
actual typealias LinkedHashMap<K, V> = HashMap<K, V>