diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index d26eeeeba10..b07df0ac87b 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -16293,6 +16293,24 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT 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 @TestMetadata("enumEntryMembers.kt") public void testEnumEntryMembers() throws Exception { diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java index d7ff8505ef2..a23a4ab2d62 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBytecodeTextTestGenerated.java @@ -2200,6 +2200,18 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest { 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 @TestMetadata("kt18731.kt") public void testKt18731() throws Exception { diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt index 7a41228a856..e5a1e5552b6 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/EnumExternalEntriesLowering.kt @@ -19,18 +19,13 @@ 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.* 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.types.* +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames internal val enumExternalEntriesPhase = makeIrFilePhase( ::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 * 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. + * + * Basically, it lowers the following code: + * ``` + * // F.kt + * JavaOrOldKotlinEnum.entries + * ``` + * + * into + * ``` + * synthetic class FKt$EntriesMappings { + * static final EnumEntries entries$1 + * static { + * entries$1 = EnumEntries(JavaOrOldKotlinEnum::values) + * } + * } + * + * // F.kt + * FKt$EntriesMappings.entries$1 + * ``` */ 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 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 && - owner.name == Name.special("") && - (parentClass.isFromJava()) // TODO check metadata for prev versions - if (!shouldBeLowered) return expression - - val enumClass = parentClass.symbol.defaultType.getClass()!! - val field = state!!.getEntriesFieldForEnum(enumClass) + owner.name == SpecialNames.ENUM_GET_ENTRIES && + (parentClass.isFromJava() || !parentClass.hasEnumEntriesFunction()) + if (!shouldBeLowered) return super.visitCall(expression) + val field = state!!.getEntriesFieldForEnum(parentClass) return IrGetFieldImpl(expression.startOffset, expression.endOffset, field.symbol, field.type) } + private fun IrClass.hasEnumEntriesFunction() = functions.any { + it.name.toString() == "" + && it.dispatchReceiverParameter == null + && it.extensionReceiverParameter == null + && it.valueParameters.isEmpty() + } + override fun visitClassNew(declaration: IrClass): IrStatement { val oldState = state val mappingState = EntriesMappingState() diff --git a/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.args b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.args new file mode 100644 index 00000000000..1ea8467ccf1 --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.args @@ -0,0 +1,3 @@ +$TESTDATA_DIR$/enumEntriesForJavaNotEnabled.kt +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.kt b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.kt new file mode 100644 index 00000000000..2d3df9319c2 --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.kt @@ -0,0 +1,6 @@ + +@OptIn(ExperimentalStdlibApi::class) +fun box(): String { + val entries = java.util.concurrent.TimeUnit.entries + return "OK" +} diff --git a/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.out b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.out new file mode 100644 index 00000000000..8c954db9195 --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesForJavaNotEnabled.out @@ -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 diff --git a/compiler/testData/cli/jvm/enumEntriesNotEnabled.args b/compiler/testData/cli/jvm/enumEntriesNotEnabled.args new file mode 100644 index 00000000000..8e809193105 --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesNotEnabled.args @@ -0,0 +1,3 @@ +$TESTDATA_DIR$/enumEntriesNotEnabled.kt +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/enumEntriesNotEnabled.kt b/compiler/testData/cli/jvm/enumEntriesNotEnabled.kt new file mode 100644 index 00000000000..63a8e1900a5 --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesNotEnabled.kt @@ -0,0 +1,9 @@ +enum class MyEnum { + OK, NOPE +} + +@OptIn(ExperimentalStdlibApi::class) +fun box(): String { + val entries = MyEnum.entries + return "OK" +} diff --git a/compiler/testData/cli/jvm/enumEntriesNotEnabled.out b/compiler/testData/cli/jvm/enumEntriesNotEnabled.out new file mode 100644 index 00000000000..edac9a3e7ef --- /dev/null +++ b/compiler/testData/cli/jvm/enumEntriesNotEnabled.out @@ -0,0 +1,4 @@ +compiler/testData/cli/jvm/enumEntriesNotEnabled.kt:7:26: error: unresolved reference: entries + val entries = MyEnum.entries + ^ +COMPILATION_ERROR diff --git a/compiler/testData/codegen/box/enum/enumEntriesFromJava.kt b/compiler/testData/codegen/box/enum/enumEntriesFromJava.kt index 43029c467c9..0c450f34134 100644 --- a/compiler/testData/codegen/box/enum/enumEntriesFromJava.kt +++ b/compiler/testData/codegen/box/enum/enumEntriesFromJava.kt @@ -13,5 +13,6 @@ enum MyEnum { fun box(): String { val entries = MyEnum.entries val entry = entries[0] + require(java.util.concurrent.TimeUnit.entries.size == java.util.concurrent.TimeUnit.values().size) return entry.toString() } diff --git a/compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt b/compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt new file mode 100644 index 00000000000..789f23a4c65 --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesMultimodule.kt @@ -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() +} diff --git a/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappings.kt b/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappings.kt new file mode 100644 index 00000000000..a540e011727 --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappings.kt @@ -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() +} diff --git a/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt b/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt new file mode 100644 index 00000000000..f8a7eab35b3 --- /dev/null +++ b/compiler/testData/codegen/box/enum/enumEntriesMultimoduleMultipleMappingsForMultipleClassfiles.kt @@ -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() +} diff --git a/compiler/testData/codegen/bytecodeText/enum/enumEntries.kt b/compiler/testData/codegen/bytecodeText/enum/enumEntries.kt index 48139ab6221..97687302431 100644 --- a/compiler/testData/codegen/bytecodeText/enum/enumEntries.kt +++ b/compiler/testData/codegen/bytecodeText/enum/enumEntries.kt @@ -10,3 +10,4 @@ enum class MyEnum { // 1 private final static synthetic Lkotlin/enums/EnumEntries; \$ENTRIES // 1 public final static getEntries\(\)Lkotlin/enums/EnumEntries; // 0 [^\$]entries +// 0 class [a-zA-Z]+\$EntriesMappings diff --git a/compiler/testData/codegen/bytecodeText/enum/enumEntriesMapping.kt b/compiler/testData/codegen/bytecodeText/enum/enumEntriesMapping.kt new file mode 100644 index 00000000000..9bc2998f348 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/enum/enumEntriesMapping.kt @@ -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 diff --git a/compiler/testData/codegen/bytecodeText/enum/enumEntriesMultiMapping.kt b/compiler/testData/codegen/bytecodeText/enum/enumEntriesMultiMapping.kt new file mode 100644 index 00000000000..7848426870e --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/enum/enumEntriesMultiMapping.kt @@ -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 diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index b9c7bb58c1e..dc108b03292 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -16293,6 +16293,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes 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 @TestMetadata("enumEntryMembers.kt") public void testEnumEntryMembers() throws Exception { diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java index e9fc27d82ef..629fab4479c 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBytecodeTextTestGenerated.java @@ -2200,6 +2200,18 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { 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 @TestMetadata("kt18731.kt") public void testKt18731() throws Exception { diff --git a/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java index c9436580fee..ea8362dfd7c 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -342,6 +342,16 @@ public class CliTestGenerated extends AbstractCliTest { 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") public void testExperimentalDeprecated() throws Exception { runTest("compiler/testData/cli/jvm/experimentalDeprecated.args"); diff --git a/core/compiler.common/src/org/jetbrains/kotlin/name/SpecialNames.kt b/core/compiler.common/src/org/jetbrains/kotlin/name/SpecialNames.kt index 61ec5e42293..5776b40f136 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/name/SpecialNames.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/name/SpecialNames.kt @@ -70,6 +70,12 @@ object SpecialNames { @JvmField val RECEIVER = Name.special("") + /** + * Kotlin-generated `entries` read-only property + */ + @JvmField + val ENUM_GET_ENTRIES = Name.special("") + /** * 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 { return name.asString().isNotEmpty() && !name.isSpecial } -} \ No newline at end of file +}