[stdlib] Improve EnumEntries
* Provide short-circuit methods for contract "EnumEntries contains all enum entries" * Make EnumEntries serializable * Introduce more convenient overload for other backends #KT-53152 Merge-request: KT-MR-6867 Merged-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
This commit is contained in:
committed by
Space
parent
cc00dcc038
commit
5054e301be
@@ -48,7 +48,7 @@ val commonMainSources by task<Sync> {
|
|||||||
"libraries/stdlib/src/kotlin/time/**",
|
"libraries/stdlib/src/kotlin/time/**",
|
||||||
"libraries/stdlib/src/kotlin/util/KotlinVersion.kt",
|
"libraries/stdlib/src/kotlin/util/KotlinVersion.kt",
|
||||||
"libraries/stdlib/src/kotlin/util/Tuples.kt",
|
"libraries/stdlib/src/kotlin/util/Tuples.kt",
|
||||||
"libraries/stdlib/src/kotlin/enums/EnumEntries.kt"
|
"libraries/stdlib/src/kotlin/enums/**"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fullCommonMainSources.outputs.files.singleFile
|
fullCommonMainSources.outputs.files.singleFile
|
||||||
@@ -96,7 +96,8 @@ val jsMainSources by task<Sync> {
|
|||||||
"libraries/stdlib/js/src/kotlin/dom/**",
|
"libraries/stdlib/js/src/kotlin/dom/**",
|
||||||
"libraries/stdlib/js/src/kotlin/browser/**",
|
"libraries/stdlib/js/src/kotlin/browser/**",
|
||||||
"libraries/stdlib/js/src/kotlinx/dom/**",
|
"libraries/stdlib/js/src/kotlinx/dom/**",
|
||||||
"libraries/stdlib/js/src/kotlinx/browser/**"
|
"libraries/stdlib/js/src/kotlinx/browser/**",
|
||||||
|
"libraries/stdlib/js/src/kotlin/enums/**"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fullJsMainSources.outputs.files.singleFile
|
fullJsMainSources.outputs.files.singleFile
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.enums
|
||||||
|
|
||||||
|
// Unused stub
|
||||||
|
internal actual class EnumEntriesSerializationProxy<E : Enum<E>> actual constructor(entries: Array<E>)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.enums
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "unused")
|
||||||
|
internal actual class EnumEntriesSerializationProxy<E : Enum<E>> actual constructor(entries: Array<E>) : Serializable {
|
||||||
|
private val c: Class<E> = entries.javaClass.componentType!! as Class<E>
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
private const val serialVersionUID: Long = 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
private fun readResolve(): Any {
|
||||||
|
return enumEntries(c.enumConstants)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
|
||||||
|
package test.enums
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import test.collections.behaviors.listBehavior
|
||||||
|
import test.collections.compare
|
||||||
|
import test.io.deserializeFromByteArray
|
||||||
|
import test.io.serializeAndDeserialize
|
||||||
|
import kotlin.enums.EnumEntries
|
||||||
|
import kotlin.enums.enumEntries
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
@Suppress("UNUSED_EXPRESSION")
|
||||||
|
class EnumEntriesJvmTest {
|
||||||
|
enum class EmptyEnum
|
||||||
|
|
||||||
|
enum class NonEmptyEnum {
|
||||||
|
A, B, C
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEmptyEnumSerialization() {
|
||||||
|
val entries = serializeAndDeserialize(enumEntries(EmptyEnum::values))
|
||||||
|
compare(EmptyEnum.values().toList(), entries) { listBehavior() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNonEmptyEnumSerialization() {
|
||||||
|
val entries = serializeAndDeserialize(enumEntries(NonEmptyEnum::values))
|
||||||
|
compare(NonEmptyEnum.values().toList(), entries) { listBehavior() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLambdaIsNotSerialized() {
|
||||||
|
val nonSerializable = object {} // Deliberately non-serializable
|
||||||
|
val entries = enumEntries {
|
||||||
|
nonSerializable // Capture it
|
||||||
|
EmptyEnum.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
val newEntries = serializeAndDeserialize(entries)
|
||||||
|
assertEquals(entries, newEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declarations for enum evolution test
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the serialized contents of
|
||||||
|
* ```
|
||||||
|
* enum class Evolved {}
|
||||||
|
* ```
|
||||||
|
* without ANY entries.
|
||||||
|
*/
|
||||||
|
private val bytes =
|
||||||
|
("-84,-19,0,5,115,114,0,42,107,111,116,108,105,110,46,101,110,117,109,115,46,69,110,117,109,69,110,116,114," +
|
||||||
|
"105,101,115,83,101,114,105,97,108,105,122,97,116,105,111,110,80,114,111,120,121,0,0,0,0,0,0,0,0,2," +
|
||||||
|
"0,1,76,0,1,99,116,0,17,76,106,97,118,97,47,108,97,110,103,47,67,108,97,115,115,59,120,112,118,114,0," +
|
||||||
|
"37,116,101,115,116,46,101,110,117,109,115,46,69,110,117,109,69,110,116,114,105,101,115,74,118,109,84," +
|
||||||
|
"101,115,116,36,69,118,111,108,118,101,100,0,0,0,0,0,0,0,0,18,0,0,120,114,0,14,106,97,118,97,46,108,97," +
|
||||||
|
"110,103,46,69,110,117,109,0,0,0,0,0,0,0,0,18,0,0,120,112")
|
||||||
|
.split(",").map { it.toInt().toByte() }.toByteArray()
|
||||||
|
|
||||||
|
// Emulate enum evolution
|
||||||
|
enum class Evolved {
|
||||||
|
E1, E2, E3
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEnumEvolution() {
|
||||||
|
// Test checks that if the enum has new members after being serialized, they are all still deserialized properly
|
||||||
|
val list = deserializeFromByteArray<EnumEntries<Evolved>>(bytes)
|
||||||
|
assertEquals(enumEntries(Evolved::values), list)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.enums
|
||||||
|
|
||||||
|
// Unused stub
|
||||||
|
internal actual class EnumEntriesSerializationProxy<E : Enum<E>> actual constructor(entries: Array<E>)
|
||||||
@@ -51,6 +51,10 @@ Low-level building blocks for libraries that provide coroutine-based APIs.
|
|||||||
|
|
||||||
Utility functions for working with the browser DOM.
|
Utility functions for working with the browser DOM.
|
||||||
|
|
||||||
|
# Package kotlin.enum
|
||||||
|
|
||||||
|
Utilities for working with Kotlin enum classes.
|
||||||
|
|
||||||
# Package kotlin.experimental
|
# Package kotlin.experimental
|
||||||
|
|
||||||
Experimental APIs, subject to change in future versions of Kotlin.
|
Experimental APIs, subject to change in future versions of Kotlin.
|
||||||
|
|||||||
@@ -21,9 +21,14 @@ public sealed interface EnumEntries<E : Enum<E>> : List<E>
|
|||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
@SinceKotlin("1.8")
|
@SinceKotlin("1.8") // Used by JVM compiler
|
||||||
internal fun <E : Enum<E>> enumEntries(entriesProvider: () -> Array<E>): EnumEntries<E> = EnumEntriesList(entriesProvider)
|
internal fun <E : Enum<E>> enumEntries(entriesProvider: () -> Array<E>): EnumEntries<E> = EnumEntriesList(entriesProvider)
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
@ExperimentalStdlibApi
|
||||||
|
@SinceKotlin("1.8") // Used by Native/JS compilers and Java serialization
|
||||||
|
internal fun <E : Enum<E>> enumEntries(entries: Array<E>): EnumEntries<E> = EnumEntriesList { entries }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For enum class E, this class is instantiated in the following manner (NB it's pseudocode that does not
|
* For enum class E, this class is instantiated in the following manner (NB it's pseudocode that does not
|
||||||
* reflect code generation strategy precisely):
|
* reflect code generation strategy precisely):
|
||||||
@@ -55,24 +60,7 @@ internal fun <E : Enum<E>> enumEntries(entriesProvider: () -> Array<E>): EnumEnt
|
|||||||
*/
|
*/
|
||||||
@SinceKotlin("1.8")
|
@SinceKotlin("1.8")
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Array<E>) : EnumEntries<E>, AbstractList<E>() {
|
private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Array<E>) : EnumEntries<E>, AbstractList<E>(), Serializable {
|
||||||
|
|
||||||
/*
|
|
||||||
* Open questions to implementation:
|
|
||||||
*
|
|
||||||
* - Are we allowed to use e.ordinal as an index?
|
|
||||||
* - e.g. indexOf(e) = e.ordinal
|
|
||||||
*
|
|
||||||
* - Are we allowed to short-circuit methods?
|
|
||||||
* - e.g. `EEL.contains(anyE)` is always true as long as no reflection is involved
|
|
||||||
*
|
|
||||||
* - Should it be Java-serializable? (then we definitely can suffer from short-circuiting and should be extra-careful around read-resolve)
|
|
||||||
*
|
|
||||||
* - Should it be sealed or just a class with internal constructor? TODO discuss on design to align this policy over all the language
|
|
||||||
* - Probably should to avoid exposing AbstractList superclass directly?
|
|
||||||
*
|
|
||||||
* - TODO package-info for kotlinlang
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Volatile // Volatile is required for safe publication of the array. It doesn't incur any real-world penalties
|
@Volatile // Volatile is required for safe publication of the array. It doesn't incur any real-world penalties
|
||||||
private var _entries: Array<E>? = null
|
private var _entries: Array<E>? = null
|
||||||
@@ -93,4 +81,33 @@ private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Ar
|
|||||||
checkElementIndex(index, entries.size)
|
checkElementIndex(index, entries.size)
|
||||||
return entries[index]
|
return entries[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// By definition, EnumEntries contains **all** enums in declaration order,
|
||||||
|
// thus we are able to short-circuit the implementation here
|
||||||
|
|
||||||
|
override fun contains(element: E): Boolean {
|
||||||
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
|
if (element === null) return false // WA for JS IR bug
|
||||||
|
// Check identity due to UnsafeVariance
|
||||||
|
val target = entries.getOrNull(element.ordinal)
|
||||||
|
return target === element
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun indexOf(element: E): Int {
|
||||||
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
|
if (element === null) return -1 // WA for JS IR bug
|
||||||
|
// Check identity due to UnsafeVariance
|
||||||
|
val ordinal = element.ordinal
|
||||||
|
val target = entries.getOrNull(ordinal)
|
||||||
|
return if (target === element) ordinal else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun lastIndexOf(element: E): Int = indexOf(element)
|
||||||
|
|
||||||
|
@Suppress("unused") // Used for Java serialization
|
||||||
|
private fun writeReplace(): Any {
|
||||||
|
return EnumEntriesSerializationProxy(entries)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal expect class EnumEntriesSerializationProxy<E : Enum<E>>(entries: Array<E>)
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ public fun <T> CompareContext<List<T>>.listBehavior() {
|
|||||||
propertyEquals { indexOf(elementAtOrNull(0)) }
|
propertyEquals { indexOf(elementAtOrNull(0)) }
|
||||||
propertyEquals { lastIndexOf(elementAtOrNull(0)) }
|
propertyEquals { lastIndexOf(elementAtOrNull(0)) }
|
||||||
|
|
||||||
|
for (element in expected) {
|
||||||
|
propertyEquals { this.indexOf(element) }
|
||||||
|
propertyEquals { this.lastIndexOf(element) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val nonExisting = object {}
|
||||||
|
propertyEquals { this.indexOf(nonExisting as Any?) }
|
||||||
|
propertyEquals { this.lastIndexOf(nonExisting as Any?) }
|
||||||
|
|
||||||
|
propertyEquals { this.indexOf(null as Any?) }
|
||||||
|
propertyEquals { this.lastIndexOf(null as Any?) }
|
||||||
|
|
||||||
propertyFails { subList(0, size + 1) }
|
propertyFails { subList(0, size + 1) }
|
||||||
propertyFails { subList(-1, 0) }
|
propertyFails { subList(-1, 0) }
|
||||||
propertyEquals { subList(0, size) }
|
propertyEquals { subList(0, size) }
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ class EnumEntriesListTest {
|
|||||||
A, B, C
|
A, B, C
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCannotBeCasted() {
|
||||||
|
val list = enumEntries(EmptyEnum::values)
|
||||||
|
assertTrue { list !is MutableList<*> }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testForEmptyEnum() {
|
fun testForEmptyEnum() {
|
||||||
val list = enumEntries(EmptyEnum::values)
|
val list = enumEntries(EmptyEnum::values)
|
||||||
@@ -54,4 +60,33 @@ class EnumEntriesListTest {
|
|||||||
val list = enumEntries(NonEmptyEnum::values)
|
val list = enumEntries(NonEmptyEnum::values)
|
||||||
compare(NonEmptyEnum.values().toList(), list) { listBehavior() }
|
compare(NonEmptyEnum.values().toList(), list) { listBehavior() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class E1 {
|
||||||
|
A
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class E2 {
|
||||||
|
A, B
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testVariantEnumBehaviour() {
|
||||||
|
val list = enumEntries(E1::values)
|
||||||
|
|
||||||
|
// Index of
|
||||||
|
val enumList: List<Enum<*>> = list
|
||||||
|
assertEquals(0, enumList.indexOf(E1.A))
|
||||||
|
assertEquals(-1, enumList.indexOf(E2.A))
|
||||||
|
assertEquals(-1, enumList.indexOf(E2.B))
|
||||||
|
|
||||||
|
// Last index of
|
||||||
|
assertEquals(0, enumList.lastIndexOf(E1.A))
|
||||||
|
assertEquals(-1, enumList.lastIndexOf(E2.A))
|
||||||
|
assertEquals(-1, enumList.lastIndexOf(E2.B))
|
||||||
|
|
||||||
|
// Contains
|
||||||
|
assertTrue(enumList.contains(E1.A))
|
||||||
|
assertFalse(enumList.contains(E2.A))
|
||||||
|
assertFalse(enumList.contains(E2.B))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -3143,6 +3143,7 @@ public abstract interface class kotlin/enums/EnumEntries : java/util/List, kotli
|
|||||||
|
|
||||||
public final class kotlin/enums/EnumEntriesKt {
|
public final class kotlin/enums/EnumEntriesKt {
|
||||||
public static final fun enumEntries (Lkotlin/jvm/functions/Function0;)Lkotlin/enums/EnumEntries;
|
public static final fun enumEntries (Lkotlin/jvm/functions/Function0;)Lkotlin/enums/EnumEntries;
|
||||||
|
public static final fun enumEntries ([Ljava/lang/Enum;)Lkotlin/enums/EnumEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface annotation class kotlin/experimental/ExperimentalObjCName : java/lang/annotation/Annotation {
|
public abstract interface annotation class kotlin/experimental/ExperimentalObjCName : java/lang/annotation/Annotation {
|
||||||
|
|||||||
Reference in New Issue
Block a user