[JVM] Lower .entries calls on Java enums
#KT-53236
This commit is contained in:
committed by
Space
parent
2ade189e60
commit
a12a31ce68
+6
@@ -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,
|
||||
|
||||
+40
-39
@@ -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))
|
||||
}
|
||||
})
|
||||
|
||||
+115
@@ -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
|
||||
}
|
||||
}
|
||||
+10
-8
@@ -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
|
||||
}
|
||||
|
||||
+1
@@ -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()
|
||||
}
|
||||
+6
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user