[JVM] Lower .entries calls on Kotlin enums
Leveraging the same mechanism with $EntriesMapping as Java enums. Old (compiled with LV/AV < 1.8) enums are detected by looking for static special <get-entries> method that cannot be introduced on Kotlin enums otherwise #KT-53236
This commit is contained in:
committed by
Space
parent
a12a31ce68
commit
e3bff290bd
+18
@@ -16293,6 +16293,24 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesFromJava.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimodule.kt")
|
||||
public void testEnumEntriesMultimodule() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleMultipleMappings.kt")
|
||||
public void testEnumEntriesMultimoduleMultipleMappings() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappings.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt")
|
||||
public void testEnumEntriesMultimoduleMultipleMappingsForMultipleClassfiles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntryMembers.kt")
|
||||
public void testEnumEntryMembers() throws Exception {
|
||||
|
||||
+12
@@ -2200,6 +2200,18 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMapping.kt")
|
||||
public void testEnumEntriesMapping() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntriesMapping.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultiMapping.kt")
|
||||
public void testEnumEntriesMultiMapping() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntriesMultiMapping.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt18731.kt")
|
||||
public void testKt18731() throws Exception {
|
||||
|
||||
+41
-17
@@ -19,18 +19,13 @@ import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
|
||||
import org.jetbrains.kotlin.ir.types.defaultType
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.isEnumClass
|
||||
import org.jetbrains.kotlin.ir.util.isFromJava
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.SpecialNames
|
||||
|
||||
internal val enumExternalEntriesPhase = makeIrFilePhase(
|
||||
::EnumExternalEntriesLowering,
|
||||
@@ -42,6 +37,25 @@ internal val enumExternalEntriesPhase = makeIrFilePhase(
|
||||
* When this lowering encounters call to `Enum.entries` where `Enum` is either Java enum or enum pre-compiled
|
||||
* with previous version of Kotlin, it generates `FileName$EntriesMapping` where it stores
|
||||
* package-private `entries` static field that is used as a replacement of missing one.
|
||||
*
|
||||
* Basically, it lowers the following code:
|
||||
* ```
|
||||
* // F.kt
|
||||
* JavaOrOldKotlinEnum.entries
|
||||
* ```
|
||||
*
|
||||
* into
|
||||
* ```
|
||||
* synthetic class FKt$EntriesMappings {
|
||||
* static final EnumEntries<JavaOrOldKotlinEnum> entries$1
|
||||
* static {
|
||||
* entries$1 = EnumEntries(JavaOrOldKotlinEnum::values)
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // F.kt
|
||||
* FKt$EntriesMappings.entries$1
|
||||
* ```
|
||||
*/
|
||||
class EnumExternalEntriesLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
||||
|
||||
@@ -78,19 +92,29 @@ class EnumExternalEntriesLowering(private val context: JvmBackendContext) : File
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
val owner = expression.symbol.owner as? IrSimpleFunction
|
||||
val parentClass = owner?.parent as? IrClass ?: return expression
|
||||
val parentClass = owner?.parent as? IrClass ?: return super.visitCall(expression)
|
||||
/*
|
||||
* Candidates for lowering:
|
||||
* * Java enums
|
||||
* * Kotlin enums that have no 'getEntries' function (thus compiled with pre-1.8 LV/AV)
|
||||
*/
|
||||
val shouldBeLowered = parentClass.isEnumClass &&
|
||||
owner.name == Name.special("<get-entries>") &&
|
||||
(parentClass.isFromJava()) // TODO check metadata for prev versions
|
||||
if (!shouldBeLowered) return expression
|
||||
|
||||
val enumClass = parentClass.symbol.defaultType.getClass()!!
|
||||
val field = state!!.getEntriesFieldForEnum(enumClass)
|
||||
owner.name == SpecialNames.ENUM_GET_ENTRIES &&
|
||||
(parentClass.isFromJava() || !parentClass.hasEnumEntriesFunction())
|
||||
if (!shouldBeLowered) return super.visitCall(expression)
|
||||
val field = state!!.getEntriesFieldForEnum(parentClass)
|
||||
return IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type)
|
||||
}
|
||||
|
||||
private fun IrClass.hasEnumEntriesFunction() = functions.any {
|
||||
it.name.toString() == "<get-entries>"
|
||||
&& it.dispatchReceiverParameter == null
|
||||
&& it.extensionReceiverParameter == null
|
||||
&& it.valueParameters.isEmpty()
|
||||
}
|
||||
|
||||
override fun visitClassNew(declaration: IrClass): IrStatement {
|
||||
val oldState = state
|
||||
val mappingState = EntriesMappingState()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
$TESTDATA_DIR$/enumEntriesForJavaNotEnabled.kt
|
||||
-d
|
||||
$TEMP_DIR$
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
val entries = java.util.concurrent.TimeUnit.entries
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.kt:4:49: error: unresolved reference: entries
|
||||
val entries = java.util.concurrent.TimeUnit.entries
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
@@ -0,0 +1,3 @@
|
||||
$TESTDATA_DIR$/enumEntriesNotEnabled.kt
|
||||
-d
|
||||
$TEMP_DIR$
|
||||
@@ -0,0 +1,9 @@
|
||||
enum class MyEnum {
|
||||
OK, NOPE
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
val entries = MyEnum.entries
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
compiler/testData/cli/jvm/enumEntriesNotEnabled.kt:7:26: error: unresolved reference: entries
|
||||
val entries = MyEnum.entries
|
||||
^
|
||||
COMPILATION_ERROR
|
||||
@@ -13,5 +13,6 @@ enum MyEnum {
|
||||
fun box(): String {
|
||||
val entries = MyEnum.entries
|
||||
val entry = entries[0]
|
||||
require(java.util.concurrent.TimeUnit.entries.size == java.util.concurrent.TimeUnit.values().size)
|
||||
return entry.toString()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: MyEnum.kt
|
||||
enum class MyEnum {
|
||||
Nope, OK
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: MyEnums.kt
|
||||
enum class MyEnum {
|
||||
N, O
|
||||
}
|
||||
|
||||
enum class MyEnum2 {
|
||||
O, K
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString() + MyEnum2.entries[1].toString()
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: MyEnums.kt
|
||||
enum class MyEnum {
|
||||
N, O
|
||||
}
|
||||
|
||||
enum class MyEnum2 {
|
||||
O, K
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
object Container {
|
||||
fun getK() = MyEnum2.entries[1].toString()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString() + Container.getK()
|
||||
}
|
||||
@@ -10,3 +10,4 @@ enum class MyEnum {
|
||||
// 1 private final static synthetic Lkotlin/enums/EnumEntries; \$ENTRIES
|
||||
// 1 public final static getEntries\(\)Lkotlin/enums/EnumEntries;
|
||||
// 0 [^\$]entries
|
||||
// 0 class [a-zA-Z]+\$EntriesMappings
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: MyEnums.kt
|
||||
enum class MyEnum {
|
||||
N, O
|
||||
}
|
||||
|
||||
enum class MyEnum2 {
|
||||
O, K
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString() + MyEnum2.entries[1].toString()
|
||||
}
|
||||
|
||||
// 1 class [a-zA-Z]+\$EntriesMappings
|
||||
@@ -0,0 +1,29 @@
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// FULL_JDK
|
||||
// WITH_STDLIB
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: MyEnums.kt
|
||||
enum class MyEnum {
|
||||
N, O
|
||||
}
|
||||
|
||||
enum class MyEnum2 {
|
||||
O, K
|
||||
}
|
||||
|
||||
// MODULE: caller(lib)
|
||||
// !LANGUAGE: +EnumEntries
|
||||
// FILE: Box.kt
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
object Container {
|
||||
fun getK() = MyEnum2.entries[1].toString()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
fun box(): String {
|
||||
return MyEnum.entries[1].toString() + Container.getK()
|
||||
}
|
||||
|
||||
// 2 class [a-zA-Z]+\$EntriesMappings
|
||||
+18
@@ -16293,6 +16293,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesFromJava.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimodule.kt")
|
||||
public void testEnumEntriesMultimodule() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleMultipleMappings.kt")
|
||||
public void testEnumEntriesMultimoduleMultipleMappings() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappings.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt")
|
||||
public void testEnumEntriesMultimoduleMultipleMappingsForMultipleClassfiles() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntryMembers.kt")
|
||||
public void testEnumEntryMembers() throws Exception {
|
||||
|
||||
+12
@@ -2200,6 +2200,18 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMapping.kt")
|
||||
public void testEnumEntriesMapping() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntriesMapping.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("enumEntriesMultiMapping.kt")
|
||||
public void testEnumEntriesMultiMapping() throws Exception {
|
||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntriesMultiMapping.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("kt18731.kt")
|
||||
public void testKt18731() throws Exception {
|
||||
|
||||
@@ -342,6 +342,16 @@ public class CliTestGenerated extends AbstractCliTest {
|
||||
runTest("compiler/testData/cli/jvm/emptySources.args");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEntriesForJavaNotEnabled.args")
|
||||
public void testEnumEntriesForJavaNotEnabled() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.args");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEntriesNotEnabled.args")
|
||||
public void testEnumEntriesNotEnabled() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/enumEntriesNotEnabled.args");
|
||||
}
|
||||
|
||||
@TestMetadata("experimentalDeprecated.args")
|
||||
public void testExperimentalDeprecated() throws Exception {
|
||||
runTest("compiler/testData/cli/jvm/experimentalDeprecated.args");
|
||||
|
||||
@@ -70,6 +70,12 @@ object SpecialNames {
|
||||
@JvmField
|
||||
val RECEIVER = Name.special("<receiver>")
|
||||
|
||||
/**
|
||||
* Kotlin-generated `entries` read-only property
|
||||
*/
|
||||
@JvmField
|
||||
val ENUM_GET_ENTRIES = Name.special("<get-entries>")
|
||||
|
||||
/**
|
||||
* Special name to store expressions used as indexes in subscription operators (`get` and `set`).
|
||||
*
|
||||
@@ -95,4 +101,4 @@ object SpecialNames {
|
||||
fun isSafeIdentifier(name: Name): Boolean {
|
||||
return name.asString().isNotEmpty() && !name.isSpecial
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user