[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");
|
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
|
@Test
|
||||||
@TestMetadata("enumEntryMembers.kt")
|
@TestMetadata("enumEntryMembers.kt")
|
||||||
public void testEnumEntryMembers() throws Exception {
|
public void testEnumEntryMembers() throws Exception {
|
||||||
|
|||||||
@@ -353,6 +353,7 @@ private val jvmFilePhases = listOf(
|
|||||||
// makePatchParentsPhase(),
|
// makePatchParentsPhase(),
|
||||||
|
|
||||||
enumClassPhase,
|
enumClassPhase,
|
||||||
|
enumExternalEntriesPhase,
|
||||||
objectClassPhase,
|
objectClassPhase,
|
||||||
readResolveForDataObjectsPhase,
|
readResolveForDataObjectsPhase,
|
||||||
staticInitializersPhase,
|
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.common.phaser.makeIrFilePhase
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
import org.jetbrains.kotlin.backend.jvm.ir.*
|
||||||
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.codegen.ImplementationBodyCodegen
|
||||||
import org.jetbrains.kotlin.config.LanguageFeature
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
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.expressions.impl.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
|
||||||
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.types.typeWith
|
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
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)
|
name = Name.identifier(ENTRIES_FIELD_NAME)
|
||||||
type = context.ir.symbols.enumEntries.defaultType
|
type = context.ir.symbols.enumEntries.defaultType
|
||||||
visibility = DescriptorVisibilities.PRIVATE
|
visibility = DescriptorVisibilities.PRIVATE
|
||||||
@@ -196,37 +191,7 @@ private class EnumClassLowering(private val context: JvmBackendContext) : ClassL
|
|||||||
isStatic = true
|
isStatic = true
|
||||||
}.apply {
|
}.apply {
|
||||||
initializer = context.createJvmIrBuilder(symbol).run {
|
initializer = context.createJvmIrBuilder(symbol).run {
|
||||||
irExprBody(irBlock {
|
irCreateEnumEntriesIndy(entriesHelper, enumArrayType, this@EnumClassLowering.context)
|
||||||
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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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)
|
super.visitClassNew(declaration)
|
||||||
|
|
||||||
for ((enum, mapping) in mappingState.mappings) {
|
for ((enum, mapping) in mappingState.mappings) {
|
||||||
val enumValues = enum.functions.single {
|
val enumValues = enum.findEnumValuesFunction(context)
|
||||||
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 builder = context.createIrBuilder(mapping.field.symbol)
|
val builder = context.createIrBuilder(mapping.field.symbol)
|
||||||
mapping.field.initializer = builder.irExprBody(builder.irBlock {
|
mapping.field.initializer = builder.irExprBody(builder.irBlock {
|
||||||
val enumSize = irCall(refArraySize).apply { dispatchReceiver = irCall(enumValues) }
|
val enumSize = irCall(refArraySize).apply { dispatchReceiver = irCall(enumValues) }
|
||||||
@@ -175,3 +168,12 @@ private class MappedEnumWhenLowering(override val context: JvmBackendContext) :
|
|||||||
return declaration
|
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_PROPERTY_REFERENCE : IrDeclarationOriginImpl("GENERATED_PROPERTY_REFERENCE", isSynthetic = true)
|
||||||
object GENERATED_MEMBER_IN_CALLABLE_REFERENCE : IrDeclarationOriginImpl("GENERATED_MEMBER_IN_CALLABLE_REFERENCE", isSynthetic = false)
|
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_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 SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
|
||||||
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
|
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
|
||||||
object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT")
|
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");
|
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
|
@Test
|
||||||
@TestMetadata("enumEntryMembers.kt")
|
@TestMetadata("enumEntryMembers.kt")
|
||||||
public void testEnumEntryMembers() throws Exception {
|
public void testEnumEntryMembers() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user