diff --git a/libraries/stdlib/api/js-v1/kotlin.enums.kt b/libraries/stdlib/api/js-v1/kotlin.enums.kt new file mode 100644 index 00000000000..df7bcdd7ed9 --- /dev/null +++ b/libraries/stdlib/api/js-v1/kotlin.enums.kt @@ -0,0 +1,4 @@ +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.8") +public sealed interface EnumEntries> : kotlin.collections.List { +} \ No newline at end of file diff --git a/libraries/stdlib/api/js/kotlin.enums.kt b/libraries/stdlib/api/js/kotlin.enums.kt new file mode 100644 index 00000000000..df7bcdd7ed9 --- /dev/null +++ b/libraries/stdlib/api/js/kotlin.enums.kt @@ -0,0 +1,4 @@ +@kotlin.ExperimentalStdlibApi +@kotlin.SinceKotlin(version = "1.8") +public sealed interface EnumEntries> : kotlin.collections.List { +} \ No newline at end of file diff --git a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts index d972a783301..e7c12398003 100644 --- a/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts +++ b/libraries/stdlib/js-ir-minimal-for-test/build.gradle.kts @@ -47,7 +47,8 @@ val commonMainSources by task { "libraries/stdlib/src/kotlin/text/**", "libraries/stdlib/src/kotlin/time/**", "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" ) ) fullCommonMainSources.outputs.files.singleFile diff --git a/libraries/stdlib/jvm/java9/module-info.java b/libraries/stdlib/jvm/java9/module-info.java index 7832d04f50a..a4c8105fd84 100644 --- a/libraries/stdlib/jvm/java9/module-info.java +++ b/libraries/stdlib/jvm/java9/module-info.java @@ -11,6 +11,7 @@ module kotlin.stdlib { exports kotlin.coroutines.cancellation; exports kotlin.coroutines.intrinsics; exports kotlin.coroutines.jvm.internal; + exports kotlin.enums; exports kotlin.io; exports kotlin.jvm; exports kotlin.jvm.functions; diff --git a/libraries/stdlib/src/kotlin/enums/EnumEntries.kt b/libraries/stdlib/src/kotlin/enums/EnumEntries.kt new file mode 100644 index 00000000000..5d4adecbab6 --- /dev/null +++ b/libraries/stdlib/src/kotlin/enums/EnumEntries.kt @@ -0,0 +1,96 @@ +/* + * 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 + +import kotlin.jvm.Volatile + +/** + * A specialized immutable implementation of [List] interface that + * contains all enum entries of the specified enum type [E]. + * [EnumEntries] contains all enum entries in the order they are declared in the source code, + * consistently with the corresponding [Enum.ordinal] values. + * + * An instance of this interface can only be obtained from `EnumClass.entries` property. + */ +@ExperimentalStdlibApi +@SinceKotlin("1.8") +public sealed interface EnumEntries> : List + +@PublishedApi +@ExperimentalStdlibApi +@SinceKotlin("1.8") +internal fun > enumEntries(entriesProvider: () -> Array): EnumEntries = EnumEntriesList(entriesProvider) + +/* + * For enum class E, this class is instantiated in the following manner (NB it's pseudocode that does not + * reflect code generation strategy precisely): + * ``` + * class E extends Enum { + * private static final E[] $VALUES + * private static final EnumEntries[] $ENTRIES + * + * static { + * $VALUES = $values(); + * val supplier = #invokedynamic ..args.. values; + * $ENTRIES = new EnumEntriesList(supplier); + * } + * + * public static EnumEntries getEntries() { + * return $ENTRIES; + * } + * + * private synthetic static E[] $values() { + * return new E[] { ... }; + * } + * } + * ``` + * + * This machinery is required as a workaround for a long-standing issue when people do reflectively change `$VALUES` of + * enums in order to workaround project-specific issues. + * We allow racy initialization (e.g. entriesProvider can be invoked multiple times), but the resulting array is safely + * published, preventing any read races after the initialization. + */ +@SinceKotlin("1.8") +@ExperimentalStdlibApi +private class EnumEntriesList>(private val entriesProvider: () -> Array) : EnumEntries, AbstractList() { + + /* + * 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 + private var _entries: Array? = null + private val entries: Array + get() { + var e = _entries + if (e != null) return e + e = entriesProvider() + _entries = e + return e + } + + override val size: Int + get() = entries.size + + override fun get(index: Int): E { + val entries = entries + checkElementIndex(index, entries.size) + return entries[index] + } +} diff --git a/libraries/stdlib/test/enums/EnumEntriesListTest.kt b/libraries/stdlib/test/enums/EnumEntriesListTest.kt new file mode 100644 index 00000000000..8742591d8be --- /dev/null +++ b/libraries/stdlib/test/enums/EnumEntriesListTest.kt @@ -0,0 +1,57 @@ +/* + * 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 kotlin.enums.enumEntries +import kotlin.test.* +import test.collections.behaviors.listBehavior +import test.collections.compare + +class EnumEntriesListTest { + + enum class EmptyEnum + + enum class NonEmptyEnum { + A, B, C + } + + @Test + fun testForEmptyEnum() { + val list = enumEntries(EmptyEnum::values) + assertTrue(list.isEmpty()) + assertEquals(0, list.size) + assertFalse { list is MutableList<*> } + assertFailsWith { list[0] } + assertFailsWith { list[-1] } + } + + @Test + fun testEmptyEnumBehaviour() { + val list = enumEntries(EmptyEnum::values) + compare(EmptyEnum.values().toList(), list) { listBehavior() } + } + + @Test + fun testForEnum() { + val list = enumEntries(NonEmptyEnum::values) + val goldenCopy = NonEmptyEnum.values().toList() + assertEquals(goldenCopy, list) + assertFalse { list is MutableList<*> } + for ((idx, e) in goldenCopy.withIndex()) { + assertEquals(e, list[e.ordinal]) + assertEquals(idx, list.indexOf(e)) + assertEquals(e, list[idx]) + } + assertFailsWith { list[-1] } + assertFailsWith { list[goldenCopy.size] } + } + + @Test + fun testyEnumBehaviour() { + val list = enumEntries(NonEmptyEnum::values) + compare(NonEmptyEnum.values().toList(), list) { listBehavior() } + } +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 60f2166ea46..7688be4f054 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -3115,6 +3115,13 @@ public abstract interface class kotlin/coroutines/jvm/internal/CoroutineStackFra public abstract fun getStackTraceElement ()Ljava/lang/StackTraceElement; } +public abstract interface class kotlin/enums/EnumEntries : java/util/List, kotlin/jvm/internal/markers/KMappedMarker { +} + +public final class kotlin/enums/EnumEntriesKt { + public static final fun enumEntries (Lkotlin/jvm/functions/Function0;)Lkotlin/enums/EnumEntries; +} + public abstract interface annotation class kotlin/experimental/ExperimentalTypeInference : java/lang/annotation/Annotation { }