[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");
|
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
|
@Test
|
||||||
@TestMetadata("enumEntryMembers.kt")
|
@TestMetadata("enumEntryMembers.kt")
|
||||||
public void testEnumEntryMembers() throws Exception {
|
public void testEnumEntryMembers() throws Exception {
|
||||||
|
|||||||
+12
@@ -2200,6 +2200,18 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
|
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
|
@Test
|
||||||
@TestMetadata("kt18731.kt")
|
@TestMetadata("kt18731.kt")
|
||||||
public void testKt18731() throws Exception {
|
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.IrField
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
|
||||||
import org.jetbrains.kotlin.ir.types.defaultType
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.getClass
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
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.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.name.SpecialNames
|
||||||
|
|
||||||
internal val enumExternalEntriesPhase = makeIrFilePhase(
|
internal val enumExternalEntriesPhase = makeIrFilePhase(
|
||||||
::EnumExternalEntriesLowering,
|
::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
|
* 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
|
* 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.
|
* 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() {
|
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 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 &&
|
val shouldBeLowered = parentClass.isEnumClass &&
|
||||||
owner.name == Name.special("<get-entries>") &&
|
owner.name == SpecialNames.ENUM_GET_ENTRIES &&
|
||||||
(parentClass.isFromJava()) // TODO check metadata for prev versions
|
(parentClass.isFromJava() || !parentClass.hasEnumEntriesFunction())
|
||||||
if (!shouldBeLowered) return expression
|
if (!shouldBeLowered) return super.visitCall(expression)
|
||||||
|
val field = state!!.getEntriesFieldForEnum(parentClass)
|
||||||
val enumClass = parentClass.symbol.defaultType.getClass()!!
|
|
||||||
val field = state!!.getEntriesFieldForEnum(enumClass)
|
|
||||||
return IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type)
|
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 {
|
override fun visitClassNew(declaration: IrClass): IrStatement {
|
||||||
val oldState = state
|
val oldState = state
|
||||||
val mappingState = EntriesMappingState()
|
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 {
|
fun box(): String {
|
||||||
val entries = MyEnum.entries
|
val entries = MyEnum.entries
|
||||||
val entry = entries[0]
|
val entry = entries[0]
|
||||||
|
require(java.util.concurrent.TimeUnit.entries.size == java.util.concurrent.TimeUnit.values().size)
|
||||||
return entry.toString()
|
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 private final static synthetic Lkotlin/enums/EnumEntries; \$ENTRIES
|
||||||
// 1 public final static getEntries\(\)Lkotlin/enums/EnumEntries;
|
// 1 public final static getEntries\(\)Lkotlin/enums/EnumEntries;
|
||||||
// 0 [^\$]entries
|
// 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");
|
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
|
@Test
|
||||||
@TestMetadata("enumEntryMembers.kt")
|
@TestMetadata("enumEntryMembers.kt")
|
||||||
public void testEnumEntryMembers() throws Exception {
|
public void testEnumEntryMembers() throws Exception {
|
||||||
|
|||||||
+12
@@ -2200,6 +2200,18 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
|
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
|
@Test
|
||||||
@TestMetadata("kt18731.kt")
|
@TestMetadata("kt18731.kt")
|
||||||
public void testKt18731() throws Exception {
|
public void testKt18731() throws Exception {
|
||||||
|
|||||||
@@ -342,6 +342,16 @@ public class CliTestGenerated extends AbstractCliTest {
|
|||||||
runTest("compiler/testData/cli/jvm/emptySources.args");
|
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")
|
@TestMetadata("experimentalDeprecated.args")
|
||||||
public void testExperimentalDeprecated() throws Exception {
|
public void testExperimentalDeprecated() throws Exception {
|
||||||
runTest("compiler/testData/cli/jvm/experimentalDeprecated.args");
|
runTest("compiler/testData/cli/jvm/experimentalDeprecated.args");
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ object SpecialNames {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val RECEIVER = Name.special("<receiver>")
|
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`).
|
* 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 {
|
fun isSafeIdentifier(name: Name): Boolean {
|
||||||
return name.asString().isNotEmpty() && !name.isSpecial
|
return name.asString().isNotEmpty() && !name.isSpecial
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user