[JVM] Lower .entries calls on Java enums

#KT-53236
This commit is contained in:
Vsevolod Tolstopyatov
2022-07-21 17:16:53 +02:00
committed by Space
parent 2ade189e60
commit a12a31ce68
8 changed files with 196 additions and 47 deletions
@@ -16287,6 +16287,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/enum/enumEntries.kt");
}
@Test
@TestMetadata("enumEntriesFromJava.kt")
public void testEnumEntriesFromJava() throws Exception {
runTest("compiler/testData/codegen/box/enum/enumEntriesFromJava.kt");
}
@Test
@TestMetadata("enumEntryMembers.kt")
public void testEnumEntryMembers() throws Exception {
@@ -353,6 +353,7 @@ private val jvmFilePhases = listOf(
// makePatchParentsPhase(),
enumClassPhase,
enumExternalEntriesPhase,
objectClassPhase,
readResolveForDataObjectsPhase,
staticInitializersPhase,
@@ -13,10 +13,7 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
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.backend.jvm.ir.*
import org.jetbrains.kotlin.codegen.ImplementationBodyCodegen
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -29,9 +26,7 @@ import org.jetbrains.kotlin.ir.expressions.*
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.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -187,7 +182,7 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL
}
}
private fun buildEntriesField(@Suppress("UNUSED_PARAMETER") entriesHelper: IrFunction): IrField = irClass.addField {
private fun buildEntriesField(entriesHelper: IrFunction): IrField = irClass.addField {
name = Name.identifier(ENTRIES_FIELD_NAME)
type = context.ir.symbols.enumEntries.defaultType
visibility = DescriptorVisibilities.PRIVATE
@@ -196,37 +191,7 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL
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))
}
})
irCreateEnumEntriesIndy(entriesHelper, enumArrayType, this@EnumClassLowering.context)
}
}
@@ -356,3 +321,39 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL
}
}
}
internal fun IrBuilderWithScope.irCreateEnumEntriesIndy(
functionThatReturnsEnumArray: IrFunction, // values() or $entries()
enumArrayType: IrType,
jvmContext: JvmBackendContext
) = irExprBody(irBlock {
val symbols = jvmContext.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[, // or values()
* ()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, functionThatReturnsEnumArray.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))
}
})
@@ -0,0 +1,115 @@
/*
* 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 org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
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.config.LanguageFeature
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.builders.declarations.addField
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
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.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.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
internal val enumExternalEntriesPhase = makeIrFilePhase(
::EnumExternalEntriesLowering,
name = "EnumExternalEntries",
description = "Replaces '.entries' on Java and pre-compiled Kotlin enums with access to entries in generated \$EntriesMapping "
)
/**
* 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.
*/
class EnumExternalEntriesLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
override fun lower(irFile: IrFile) {
if (!context.state.languageVersionSettings.supportsFeature(LanguageFeature.EnumEntries)) {
return
}
irFile.transformChildrenVoid(this)
}
private var state: EntriesMappingState? = null
private inner class EntriesMappingState {
val mappings = mutableMapOf<IrClass /* enum */, IrField>()
val mappingsClass by lazy {
context.irFactory.buildClass {
name = Name.identifier("EntriesMappings")
origin = JvmLoweredDeclarationOrigin.ENUM_MAPPINGS_FOR_ENTRIES
}.apply {
createImplicitParameterDeclarationWithWrappedDescriptor()
}
}
fun getEntriesFieldForEnum(enumClass: IrClass): IrField {
return mappings.getOrPut(enumClass) {
mappingsClass.addField {
name = Name.identifier("entries\$${mappings.size}")
type = context.ir.symbols.enumEntries.typeWith(enumClass.defaultType)
origin = JvmLoweredDeclarationOrigin.ENUM_MAPPINGS_FOR_ENTRIES
isFinal = true
isStatic = true
}
}
}
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
val owner = expression.symbol.owner as? IrSimpleFunction
val parentClass = owner?.parent as? IrClass ?: return expression
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)
return IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type)
}
override fun visitClassNew(declaration: IrClass): IrStatement {
val oldState = state
val mappingState = EntriesMappingState()
state = mappingState
super.visitClassNew(declaration)
for ((enum, field) in mappingState.mappings) {
val enumValues = enum.findEnumValuesFunction(context)
val enumArrayType = field.type
val builder = context.createIrBuilder(field.symbol)
field.initializer = builder.irCreateEnumEntriesIndy(enumValues, enumArrayType, context)
}
if (mappingState.mappings.isNotEmpty()) {
declaration.declarations += mappingState.mappingsClass.apply {
parent = declaration
}
}
state = oldState
return declaration
}
}
@@ -127,14 +127,7 @@ private class MappedEnumWhenLowering(override val context: JvmBackendContext) :
super.visitClassNew(declaration)
for ((enum, mapping) in mappingState.mappings) {
val enumValues = enum.functions.single {
it.name.toString() == "values"
&& it.dispatchReceiverParameter == null
&& it.extensionReceiverParameter == null
&& it.valueParameters.isEmpty()
&& it.returnType.isBoxedArray
&& it.returnType.getArrayElementType(context.irBuiltIns).classOrNull == enum.symbol
}
val enumValues = enum.findEnumValuesFunction(context)
val builder = context.createIrBuilder(mapping.field.symbol)
mapping.field.initializer = builder.irExprBody(builder.irBlock {
val enumSize = irCall(refArraySize).apply { dispatchReceiver = irCall(enumValues) }
@@ -175,3 +168,12 @@ private class MappedEnumWhenLowering(override val context: JvmBackendContext) :
return declaration
}
}
internal fun IrClass.findEnumValuesFunction(context: JvmBackendContext) = functions.single {
it.name.toString() == "values"
&& it.dispatchReceiverParameter == null
&& it.extensionReceiverParameter == null
&& it.valueParameters.isEmpty()
&& it.returnType.isBoxedArray
&& it.returnType.getArrayElementType(context.irBuiltIns).classOrNull == this.symbol
}
@@ -27,6 +27,7 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
object GENERATED_PROPERTY_REFERENCE : IrDeclarationOriginImpl("GENERATED_PROPERTY_REFERENCE", isSynthetic = true)
object GENERATED_MEMBER_IN_CALLABLE_REFERENCE : IrDeclarationOriginImpl("GENERATED_MEMBER_IN_CALLABLE_REFERENCE", isSynthetic = false)
object ENUM_MAPPINGS_FOR_WHEN : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_WHEN", isSynthetic = true)
object ENUM_MAPPINGS_FOR_ENTRIES : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_ENTRIES", isSynthetic = true)
object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT")
@@ -0,0 +1,17 @@
// !LANGUAGE: +EnumEntries
// TARGET_BACKEND: JVM_IR
// FULL_JDK
// WITH_STDLIB
// FILE: MyEnum.java
enum MyEnum {
OK, NOPE
}
// FILE: test.kt
@OptIn(ExperimentalStdlibApi::class)
fun box(): String {
val entries = MyEnum.entries
val entry = entries[0]
return entry.toString()
}
@@ -16287,6 +16287,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/enum/enumEntries.kt");
}
@Test
@TestMetadata("enumEntriesFromJava.kt")
public void testEnumEntriesFromJava() throws Exception {
runTest("compiler/testData/codegen/box/enum/enumEntriesFromJava.kt");
}
@Test
@TestMetadata("enumEntryMembers.kt")
public void testEnumEntryMembers() throws Exception {