[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/util/KotlinVersion.kt",
|
||||
"libraries/stdlib/src/kotlin/util/Tuples.kt",
|
||||
"libraries/stdlib/src/kotlin/enums/EnumEntries.kt"
|
||||
"libraries/stdlib/src/kotlin/enums/**"
|
||||
)
|
||||
)
|
||||
fullCommonMainSources.outputs.files.singleFile
|
||||
@@ -96,7 +96,8 @@ val jsMainSources by task<Sync> {
|
||||
"libraries/stdlib/js/src/kotlin/dom/**",
|
||||
"libraries/stdlib/js/src/kotlin/browser/**",
|
||||
"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
|
||||
|
||||
@@ -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.
|
||||
|
||||
# Package kotlin.enum
|
||||
|
||||
Utilities for working with Kotlin enum classes.
|
||||
|
||||
# Package kotlin.experimental
|
||||
|
||||
Experimental APIs, subject to change in future versions of Kotlin.
|
||||
|
||||
@@ -21,9 +21,14 @@ public sealed interface EnumEntries<E : Enum<E>> : List<E>
|
||||
|
||||
@PublishedApi
|
||||
@ExperimentalStdlibApi
|
||||
@SinceKotlin("1.8")
|
||||
@SinceKotlin("1.8") // Used by JVM compiler
|
||||
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
|
||||
* reflect code generation strategy precisely):
|
||||
@@ -55,24 +60,7 @@ internal fun <E : Enum<E>> enumEntries(entriesProvider: () -> Array<E>): EnumEnt
|
||||
*/
|
||||
@SinceKotlin("1.8")
|
||||
@ExperimentalStdlibApi
|
||||
private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Array<E>) : EnumEntries<E>, AbstractList<E>() {
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Array<E>) : EnumEntries<E>, AbstractList<E>(), Serializable {
|
||||
|
||||
@Volatile // Volatile is required for safe publication of the array. It doesn't incur any real-world penalties
|
||||
private var _entries: Array<E>? = null
|
||||
@@ -93,4 +81,33 @@ private class EnumEntriesList<E : Enum<E>>(private val entriesProvider: () -> Ar
|
||||
checkElementIndex(index, entries.size)
|
||||
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 { 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(-1, 0) }
|
||||
propertyEquals { subList(0, size) }
|
||||
|
||||
@@ -18,6 +18,12 @@ class EnumEntriesListTest {
|
||||
A, B, C
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCannotBeCasted() {
|
||||
val list = enumEntries(EmptyEnum::values)
|
||||
assertTrue { list !is MutableList<*> }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForEmptyEnum() {
|
||||
val list = enumEntries(EmptyEnum::values)
|
||||
@@ -54,4 +60,33 @@ class EnumEntriesListTest {
|
||||
val list = enumEntries(NonEmptyEnum::values)
|
||||
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 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 {
|
||||
|
||||
Reference in New Issue
Block a user