[JVM] Support Enum.entries for enums being compiled with Kotlin 1.8+

#KT-53236
This commit is contained in:
Vsevolod Tolstopyatov
2022-07-21 10:54:28 +02:00
committed by Space
parent 1b6a43ba69
commit e708809e55
13 changed files with 328 additions and 16 deletions
@@ -16281,6 +16281,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/enum/enumConstructorParameterClashWithDefaults.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/codegen/box/enum/enumEntries.kt");
}
@Test
@TestMetadata("enumEntryMembers.kt")
public void testEnumEntryMembers() throws Exception {
@@ -2194,6 +2194,12 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/enum/enumCheckcasts.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
}
@Test
@TestMetadata("kt18731.kt")
public void testKt18731() throws Exception {
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.
*/
@@ -14,23 +14,23 @@ import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.getSingleAbstractMethod
import org.jetbrains.kotlin.backend.jvm.ir.irArray
import org.jetbrains.kotlin.backend.jvm.ir.javaClassReference
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrSetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
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.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
@@ -45,18 +45,59 @@ internal val enumClassPhase = makeIrFilePhase(
)
private const val VALUES_HELPER_FUNCTION_NAME = "\$values"
private const val ENTRIES_HELPER_FUNCTION_NAME = "\$entries"
private const val ENTRIES_FIELD_NAME = "\$ENTRIES"
private class EnumClassLowering(private val context: JvmBackendContext) : ClassLoweringPass {
/*
* Example of codegen for
* `enum class MyEnum { A }`
*
* ```
* enum MyEnum extends Enum<MyEnum> {
* private static final synthetic MyEnum[] $VALUES
* private static final synthetic List<MyEnum> $ENTRIES;
*
* <clinit> {
* A = new MyEnum("A", 0);
* $VALUES = $values();
* Function0<MyEnum[]> supplier = #invokedynamic ..args.. $entries;
* $ENTRIES = new EnumEntriesList(supplier);
* }
*
* public static MyEnum[] values() {
* return $VALUES.clone();
* }
*
* // Should be RO property from Kotlin standpoint
* public static List<MyEnum> getEntries() {
* return $ENTRIES;
* }
*
* private synthetic static MyEnum[] $values() {
* return new MyEnum[] { A };
* }
*
* private synthetic static MyEnum[] $entries() {
* return $VALUES
* }
* }
* ```
*/
private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringPass {
override fun lower(irClass: IrClass) {
if (!irClass.isEnumClass) return
EnumClassTransformer(irClass).run()
// Also protected by API version check as it relies on EnumEntries in standard library
EnumClassTransformer(irClass, context.state.languageVersionSettings.supportsFeature(LanguageFeature.EnumEntries)).run()
}
private inner class EnumClassTransformer(val irClass: IrClass) {
private inner class EnumClassTransformer(private val irClass: IrClass, private val supportsEnumEntries: Boolean) {
private val loweredEnumConstructors = hashMapOf<IrConstructorSymbol, IrConstructor>()
private val loweredEnumConstructorParameters = hashMapOf<IrValueParameterSymbol, IrValueParameter>()
private val enumEntryOrdinals = TObjectIntHashMap<IrEnumEntry>()
private val declarationToEnumEntry = mutableMapOf<IrDeclaration, IrEnumEntry>()
private val enumArrayType = context.irBuiltIns.arrayClass.typeWith(irClass.defaultType) // Enum[]
fun run() {
// Lower IrEnumEntry into IrField and IrClass members
@@ -75,8 +116,25 @@ private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringP
// Construct the synthetic $VALUES field, which contains an array of all enum entries by calling $values()
val valuesField = buildValuesField(valuesHelperFunction)
val entriesField: IrField?
if (supportsEnumEntries) {
// Constructs the synthetic $entries() function that returns plain $VALUES without copy
val entriesHelperFunction = buildEntriesHelperFunction(valuesField)
/*
* Add synthetic $ENTRIES field and binds its initializer to
* ```
* val supplier: () -> E[] = indy LMF $entries
* $ENTRIES = EnumEntries(supplier)
* ```
*/
entriesField = buildEntriesField(entriesHelperFunction)
} else {
entriesField = null
}
// Add synthetic parameters to enum constructors and implement the values and valueOf functions
irClass.transformChildrenVoid(EnumClassDeclarationsTransformer(valuesField))
irClass.transformChildrenVoid(EnumClassDeclarationsTransformer(valuesField, entriesField))
// Add synthetic arguments to enum constructor calls and remap enum constructor parameters
irClass.transformChildrenVoid(EnumClassCallTransformer())
@@ -85,12 +143,12 @@ private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringP
private fun buildEnumEntryField(enumEntry: IrEnumEntry): IrField =
context.cachedDeclarations.getFieldForEnumEntry(enumEntry).apply {
initializer = IrExpressionBodyImpl(enumEntry.initializerExpression!!.expression.patchDeclarationParents(this))
annotations += enumEntry.annotations
annotations = annotations + enumEntry.annotations
}
private fun buildValuesHelperFunction(): IrFunction = irClass.addFunction {
name = Name.identifier(VALUES_HELPER_FUNCTION_NAME)
returnType = context.irBuiltIns.arrayClass.typeWith(irClass.defaultType)
returnType = enumArrayType
visibility = DescriptorVisibilities.PRIVATE
origin = IrDeclarationOrigin.SYNTHETIC_HELPER_FOR_ENUM_VALUES
}.apply {
@@ -103,9 +161,20 @@ private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringP
}
}
private fun buildEntriesHelperFunction(valuesField: IrField): IrFunction = irClass.addFunction {
name = Name.identifier(ENTRIES_HELPER_FUNCTION_NAME)
returnType = enumArrayType
visibility = DescriptorVisibilities.PRIVATE
origin = IrDeclarationOrigin.SYNTHETIC_HELPER_FOR_ENUM_VALUES
}.apply {
body = context.createJvmIrBuilder(symbol).run {
irExprBody(irGetField(null, valuesField))
}
}
private fun buildValuesField(valuesHelperFunction: IrFunction): IrField = irClass.addField {
name = Name.identifier(ImplementationBodyCodegen.ENUM_VALUES_FIELD_NAME)
type = context.irBuiltIns.arrayClass.typeWith(irClass.defaultType)
type = enumArrayType
visibility = DescriptorVisibilities.PRIVATE
origin = IrDeclarationOrigin.FIELD_FOR_ENUM_VALUES
isFinal = true
@@ -118,7 +187,53 @@ private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringP
}
}
private inner class EnumClassDeclarationsTransformer(val valuesField: IrField) : IrElementTransformerVoid() {
private fun buildEntriesField(@Suppress("UNUSED_PARAMETER") entriesHelper: IrFunction): IrField = irClass.addField {
name = Name.identifier(ENTRIES_FIELD_NAME)
type = context.ir.symbols.enumEntries.defaultType
visibility = DescriptorVisibilities.PRIVATE
origin = IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRIES
isFinal = true
isStatic = true
}.apply {
initializer = context.createJvmIrBuilder(symbol).run {
irExprBody(irBlock {
val symbols = this@EnumClassLowering.context.ir.symbols
val samClass = context.irBuiltIns.functionN(0)
val type = samClass.typeWith(enumArrayType)
val sam = type.getClass()!!.getSingleAbstractMethod()!!.symbol
/*
* Indy to LMF call:
* INVOKEDYNAMIC get()Lkotlin/jvm/functions/Function0; [
* // handle kind 0x6 : INVOKESTATIC
* java/lang/invoke/LambdaMetafactory.metafactory
* // arguments:
* ()Ljava/lang/Object;,
* // handle kind 0x6 : INVOKESTATIC
* $entries() [LEnum[,
* ()Ljava/util/List;
* ]
*/
val indyCall = irCall(symbols.indyLambdaMetafactoryIntrinsic, type).apply {
putTypeArgument(0, type)
putValueArgument(0, irRawFunctionReferefence(context.irBuiltIns.anyType, sam))
putValueArgument(1, IrFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, entriesHelper.symbol, 0, 0))
putValueArgument(2, irRawFunctionReferefence(context.irBuiltIns.anyType, sam))
putValueArgument(3, irVararg(context.irBuiltIns.anyType, emptyList()))
putValueArgument(4, irBoolean(false))
}
// Bind it to temp var and pass to ctor
val supplier = createTmpVariable(indyCall, "supplier")
+irCall(symbols.createEnumEntries).apply {
putValueArgument(0, irGet(supplier))
}
})
}
}
private inner class EnumClassDeclarationsTransformer(
private val valuesField: IrField, private val entriesField: IrField?
) : IrElementTransformerVoid() {
override fun visitClass(declaration: IrClass): IrStatement =
if (declaration.isEnumEntry) super.visitClass(declaration) else declaration
@@ -165,6 +280,12 @@ private class EnumClassLowering(val context: JvmBackendContext) : ClassLoweringP
putValueArgument(0, javaClassReference(irClass.defaultType))
putValueArgument(1, irGet(declaration.valueParameters[0]))
}
IrSyntheticBodyKind.ENUM_ENTRIES -> {
// We're ensuring on FE level that this declaration exists only
// when the corresponding flag is set up (-> entriesField is never null)
irGetField(null, entriesField!!)
}
}
)
}
@@ -55,6 +55,7 @@ class JvmSymbols(
private val kotlinJvmPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm"))
private val kotlinJvmInternalPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.internal"))
private val kotlinJvmFunctionsPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.functions"))
private val kotlinEnumPackage: IrPackageFragment = createPackage(FqName("kotlin.enums"))
private val kotlinReflectPackage: IrPackageFragment = createPackage(FqName("kotlin.reflect"))
private val javaLangPackage: IrPackageFragment = createPackage(FqName("java.lang"))
private val javaLangInvokePackage: IrPackageFragment = createPackage(FqName("java.lang.invoke"))
@@ -96,6 +97,7 @@ class JvmSymbols(
"kotlin" -> kotlinPackage
"kotlin.coroutines" -> kotlinCoroutinesPackage
"kotlin.coroutines.jvm.internal" -> kotlinCoroutinesJvmInternalPackage
"kotlin.enums" -> kotlinEnumPackage
"kotlin.jvm.internal" -> kotlinJvmInternalPackage
"kotlin.jvm.functions" -> kotlinJvmFunctionsPackage
"kotlin.jvm" -> kotlinJvmPackage
@@ -214,6 +216,20 @@ class JvmSymbols(
}
}
val enumEntries: IrClassSymbol = createClass(FqName("kotlin.enums.EnumEntries"), ClassKind.INTERFACE) { klass ->
// Actually it is E : Enum<E>, but doesn't seem to have any effect yet
klass.addTypeParameter("E", irBuiltIns.anyNType)
}
private val enumEntriesKt: IrClassSymbol = createClass(FqName("kotlin.enums.EnumEntriesKt")) { klass ->
klass.addFunction("enumEntries", enumEntries.defaultType, isStatic = true).apply {
addValueParameter("entriesProvider",
irBuiltIns.functionN(0).typeWith(irBuiltIns.arrayClass.typeWith(klass.typeParameters.map { it.defaultType })))
}
}
val createEnumEntries: IrSimpleFunctionSymbol = enumEntriesKt.functions.single { it.owner.name.asString() == "enumEntries" }
override val defaultConstructorMarker: IrClassSymbol =
createClass(FqName("kotlin.jvm.internal.DefaultConstructorMarker"))
@@ -60,7 +60,10 @@ interface IrDeclarationOrigin {
object FIELD_FOR_ENUM_ENTRY : IrDeclarationOriginImpl("FIELD_FOR_ENUM_ENTRY")
object SYNTHETIC_HELPER_FOR_ENUM_VALUES : IrDeclarationOriginImpl("SYNTHETIC_HELPER_FOR_ENUM_VALUES", isSynthetic = true)
object SYNTHETIC_HELPER_FOR_ENUM_ENTRIES : IrDeclarationOriginImpl("SYNTHETIC_HELPER_FOR_ENUM_ENTRIES", isSynthetic = true)
object FIELD_FOR_ENUM_VALUES : IrDeclarationOriginImpl("FIELD_FOR_ENUM_VALUES", isSynthetic = true)
object FIELD_FOR_ENUM_ENTRIES: IrDeclarationOriginImpl("FIELD_FOR_ENUM_ENTRIES", isSynthetic = true)
object PROPERTY_FOR_ENUM_ENTRIES: IrDeclarationOriginImpl("PROPERTY_FOR_ENUM_ENTRIES", isSynthetic = false)
object FIELD_FOR_OBJECT_INSTANCE : IrDeclarationOriginImpl("FIELD_FOR_OBJECT_INSTANCE")
object FIELD_FOR_CLASS_CONTEXT_RECEIVER : IrDeclarationOriginImpl("FIELD_FOR_CLASS_CONTEXT_RECEIVER", isSynthetic = true)
+15
View File
@@ -0,0 +1,15 @@
// !LANGUAGE: +EnumEntries
// TARGET_BACKEND: JVM_IR
// FULL_JDK
// WITH_STDLIB
enum class MyEnum {
OK, NOPE
}
@OptIn(ExperimentalStdlibApi::class)
fun box(): String {
val entries = MyEnum.entries
val entry = entries[0]
return entry.toString()
}
@@ -0,0 +1,26 @@
// TARGET_BACKEND: JVM_IR
// WITH_RUNTIME
// !LANGUAGE: +EnumEntries
enum class SimpleEnum {
A, B, C
}
enum class WithConstructor(val x: String) {
A("1"), B("2"), C("3")
}
enum class WithEntryClass {
A {
override fun foo() {}
}
;
abstract fun foo()
}
annotation class Ann
enum class WithAnnotations {
@Ann A, @Ann B
}
@@ -0,0 +1,83 @@
@java.lang.annotation.Retention(value=RUNTIME)
@kotlin.Metadata
public annotation class Ann {
// source: 'enumEntries.kt'
}
@kotlin.Metadata
public final enum class SimpleEnum {
// source: 'enumEntries.kt'
private synthetic final static field $ENTRIES: kotlin.enums.EnumEntries
private synthetic final static field $VALUES: SimpleEnum[]
public final enum static field A: SimpleEnum
public final enum static field B: SimpleEnum
public final enum static field C: SimpleEnum
private synthetic final static method $entries(): SimpleEnum[]
private synthetic final static method $values(): SimpleEnum[]
static method <clinit>(): void
private method <init>(p0: java.lang.String, p1: int): void
public final static @org.jetbrains.annotations.NotNull method getEntries(): kotlin.enums.EnumEntries
public static method valueOf(p0: java.lang.String): SimpleEnum
public static method values(): SimpleEnum[]
}
@kotlin.Metadata
public final enum class WithAnnotations {
// source: 'enumEntries.kt'
private synthetic final static field $ENTRIES: kotlin.enums.EnumEntries
private synthetic final static field $VALUES: WithAnnotations[]
public final enum static @Ann field A: WithAnnotations
public final enum static @Ann field B: WithAnnotations
private synthetic final static method $entries(): WithAnnotations[]
private synthetic final static method $values(): WithAnnotations[]
static method <clinit>(): void
private method <init>(p0: java.lang.String, p1: int): void
public final static @org.jetbrains.annotations.NotNull method getEntries(): kotlin.enums.EnumEntries
public static method valueOf(p0: java.lang.String): WithAnnotations
public static method values(): WithAnnotations[]
}
@kotlin.Metadata
public final enum class WithConstructor {
// source: 'enumEntries.kt'
private synthetic final static field $ENTRIES: kotlin.enums.EnumEntries
private synthetic final static field $VALUES: WithConstructor[]
public final enum static field A: WithConstructor
public final enum static field B: WithConstructor
public final enum static field C: WithConstructor
private final @org.jetbrains.annotations.NotNull field x: java.lang.String
private synthetic final static method $entries(): WithConstructor[]
private synthetic final static method $values(): WithConstructor[]
static method <clinit>(): void
private method <init>(p0: java.lang.String, p1: int, p2: java.lang.String): void
public final static @org.jetbrains.annotations.NotNull method getEntries(): kotlin.enums.EnumEntries
public final @org.jetbrains.annotations.NotNull method getX(): java.lang.String
public static method valueOf(p0: java.lang.String): WithConstructor
public static method values(): WithConstructor[]
}
@kotlin.Metadata
final class WithEntryClass$A {
// source: 'enumEntries.kt'
final inner class WithEntryClass$A
method <init>(p0: java.lang.String, p1: int): void
public method foo(): void
}
@kotlin.Metadata
public abstract enum class WithEntryClass {
// source: 'enumEntries.kt'
private synthetic final static field $ENTRIES: kotlin.enums.EnumEntries
private synthetic final static field $VALUES: WithEntryClass[]
public final enum static field A: WithEntryClass
final inner class WithEntryClass$A
private synthetic final static method $entries(): WithEntryClass[]
private synthetic final static method $values(): WithEntryClass[]
static method <clinit>(): void
private method <init>(p0: java.lang.String, p1: int): void
public synthetic method <init>(p0: java.lang.String, p1: int, p2: kotlin.jvm.internal.DefaultConstructorMarker): void
public abstract method foo(): void
public final static @org.jetbrains.annotations.NotNull method getEntries(): kotlin.enums.EnumEntries
public static method valueOf(p0: java.lang.String): WithEntryClass
public static method values(): WithEntryClass[]
}
@@ -0,0 +1,12 @@
// TARGET_BACKEND: JVM_IR
// !LANGUAGE: +EnumEntries
enum class MyEnum {
E
}
// 1 INVOKEDYNAMIC
// 1 kotlin.enums.EnumEntries<MyEnum> getEntries\(\)
// 1 private final static synthetic Lkotlin/enums/EnumEntries; \$ENTRIES
// 1 public final static getEntries\(\)Lkotlin/enums/EnumEntries;
// 0 [^\$]entries
@@ -16281,6 +16281,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/enum/enumConstructorParameterClashWithDefaults.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/codegen/box/enum/enumEntries.kt");
}
@Test
@TestMetadata("enumEntryMembers.kt")
public void testEnumEntryMembers() throws Exception {
@@ -109,6 +109,12 @@ public class IrBytecodeListingTestGenerated extends AbstractIrBytecodeListingTes
runTest("compiler/testData/codegen/bytecodeListing/enum.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/codegen/bytecodeListing/enumEntries.kt");
}
@Test
@TestMetadata("extension.kt")
public void testExtension() throws Exception {
@@ -2194,6 +2194,12 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/enum/enumCheckcasts.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/enum/enumEntries.kt");
}
@Test
@TestMetadata("kt18731.kt")
public void testKt18731() throws Exception {
@@ -136,6 +136,12 @@ public class IrTextTestGenerated extends AbstractIrTextTest {
runTest("compiler/testData/ir/irText/classes/enumClassModality.kt");
}
@Test
@TestMetadata("enumEntries.kt")
public void testEnumEntries() throws Exception {
runTest("compiler/testData/ir/irText/classes/enumEntries.kt");
}
@Test
@TestMetadata("enumWithMultipleCtors.kt")
public void testEnumWithMultipleCtors() throws Exception {