IR: add IrClass.hasEnumEntries

This flag will be used on JVM to determine whether or not to generate
external enum entries mappings ("$EntriesMappings") classes. Note that
from the frontend's point of view, every enum has `entries`, so for
backend purposes we have to reach out to the underlying deserialized
data to read the flag from the metadata.
This commit is contained in:
Alexander Udalov
2023-08-17 12:54:59 +02:00
committed by Space Team
parent db31f1f926
commit c33c918bd4
19 changed files with 68 additions and 10 deletions
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.containingClassForLocalAttr
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.* import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.expressions.FirAnonymousObjectExpression import org.jetbrains.kotlin.fir.expressions.FirAnonymousObjectExpression
import org.jetbrains.kotlin.fir.hasEnumEntries
import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyClass import org.jetbrains.kotlin.fir.lazy.Fir2IrLazyClass
import org.jetbrains.kotlin.fir.moduleData import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.getSymbolByLookupTag import org.jetbrains.kotlin.fir.resolve.getSymbolByLookupTag
@@ -138,7 +139,8 @@ class Fir2IrClassifiersGenerator(val components: Fir2IrComponents) : Fir2IrCompo
isData = regularClass.isData, isData = regularClass.isData,
isValue = regularClass.isInline, isValue = regularClass.isInline,
isExpect = regularClass.isExpect, isExpect = regularClass.isExpect,
isFun = regularClass.isFun isFun = regularClass.isFun,
hasEnumEntries = regularClass.hasEnumEntries,
).apply { ).apply {
metadata = FirMetadataSource.Class(regularClass) metadata = FirMetadataSource.Class(regularClass)
} }
@@ -376,7 +378,8 @@ class Fir2IrClassifiersGenerator(val components: Fir2IrComponents) : Fir2IrCompo
isData = false, isData = false,
isValue = false, isValue = false,
isExpect = false, isExpect = false,
isFun = false isFun = false,
hasEnumEntries = false,
).apply { ).apply {
metadata = FirMetadataSource.CodeFragment(codeFragment) metadata = FirMetadataSource.CodeFragment(codeFragment)
parent = containingFile parent = containingFile
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.backend.*
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.* import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.dispatchReceiverClassLookupTagOrNull import org.jetbrains.kotlin.fir.dispatchReceiverClassLookupTagOrNull
import org.jetbrains.kotlin.fir.hasEnumEntries
import org.jetbrains.kotlin.fir.isNewPlaceForBodyGeneration import org.jetbrains.kotlin.fir.isNewPlaceForBodyGeneration
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope import org.jetbrains.kotlin.fir.scopes.FirContainingNamesAwareScope
@@ -109,6 +110,10 @@ class Fir2IrLazyClass(
get() = fir.isFun get() = fir.isFun
set(_) = mutationNotSupported() set(_) = mutationNotSupported()
override var hasEnumEntries: Boolean
get() = fir.hasEnumEntries
set(_) = mutationNotSupported()
override var superTypes: List<IrType> by lazyVar(lock) { override var superTypes: List<IrType> by lazyVar(lock) {
fir.superTypeRefs.map { it.toIrType(typeConverter) } fir.superTypeRefs.map { it.toIrType(typeConverter) }
} }
@@ -51,6 +51,17 @@ abstract class IrClass : IrDeclarationBase(), IrPossiblyExternalDeclaration,
abstract var isFun: Boolean abstract var isFun: Boolean
/**
* Returns true iff this is a class loaded from dependencies which has the `HAS_ENUM_ENTRIES`
* metadata flag set.
* This flag is useful for Kotlin/JVM to determine whether an enum class from dependency
* actually has the `entries` property
* in its bytecode, as opposed to whether it has it in its member scope, which is true even for
* enum classes compiled by
* old versions of Kotlin which did not support the EnumEntries language feature.
*/
abstract var hasEnumEntries: Boolean
abstract val source: SourceElement abstract val source: SourceElement
abstract var superTypes: List<IrType> abstract var superTypes: List<IrType>
@@ -60,6 +60,7 @@ interface IrFactory {
isValue: Boolean = false, isValue: Boolean = false,
isExpect: Boolean = false, isExpect: Boolean = false,
isFun: Boolean = false, isFun: Boolean = false,
hasEnumEntries: Boolean = false,
source: SourceElement = SourceElement.NO_SOURCE, source: SourceElement = SourceElement.NO_SOURCE,
): IrClass ): IrClass
@@ -300,6 +301,7 @@ interface IrFactory {
isValue, isValue,
isExpect, isExpect,
isFun, isFun,
false,
source, source,
) )
@@ -20,6 +20,7 @@ class IrClassBuilder : IrDeclarationBuilder() {
var isValue: Boolean = false var isValue: Boolean = false
var isExpect: Boolean = false var isExpect: Boolean = false
var isFun: Boolean = false var isFun: Boolean = false
var hasEnumEntries: Boolean = false
fun updateFrom(from: IrClass) { fun updateFrom(from: IrClass) {
super.updateFrom(from) super.updateFrom(from)
@@ -33,5 +34,6 @@ class IrClassBuilder : IrDeclarationBuilder() {
isValue = from.isValue isValue = from.isValue
isExpect = from.isExpect isExpect = from.isExpect
isFun = from.isFun isFun = from.isFun
hasEnumEntries = from.hasEnumEntries
} }
} }
@@ -42,6 +42,7 @@ internal fun IrFactory.buildClass(builder: IrClassBuilder): IrClass = with(build
isValue = isValue, isValue = isValue,
isExpect = isExpect, isExpect = isExpect,
isFun = isFun, isFun = isFun,
hasEnumEntries = hasEnumEntries,
) )
} }
@@ -30,6 +30,7 @@ open class IrClassImpl(
override var isValue: Boolean = false, override var isValue: Boolean = false,
override var isExpect: Boolean = false, override var isExpect: Boolean = false,
override var isFun: Boolean = false, override var isFun: Boolean = false,
override var hasEnumEntries: Boolean = false,
override val source: SourceElement = SourceElement.NO_SOURCE, override val source: SourceElement = SourceElement.NO_SOURCE,
override val factory: IrFactory = IrFactoryImpl override val factory: IrFactory = IrFactoryImpl
) : IrClass() { ) : IrClass() {
@@ -50,11 +50,12 @@ abstract class AbstractIrFactoryImpl : IrFactory {
isValue: Boolean, isValue: Boolean,
isExpect: Boolean, isExpect: Boolean,
isFun: Boolean, isFun: Boolean,
hasEnumEntries: Boolean,
source: SourceElement, source: SourceElement,
): IrClass = ): IrClass =
IrClassImpl( IrClassImpl(
startOffset, endOffset, origin, symbol, name, kind, visibility, modality, startOffset, endOffset, origin, symbol, name, kind, visibility, modality,
isCompanion, isInner, isData, isExternal, isValue, isExpect, isFun, source, isCompanion, isInner, isData, isExternal, isValue, isExpect, isFun, hasEnumEntries, source,
factory = this factory = this
) )
@@ -62,6 +62,7 @@ class IrFactoryImplForJsIC(override val stageController: StageController) : Abst
isValue: Boolean, isValue: Boolean,
isExpect: Boolean, isExpect: Boolean,
isFun: Boolean, isFun: Boolean,
hasEnumEntries: Boolean,
source: SourceElement source: SourceElement
): IrClass { ): IrClass {
return super.createClass( return super.createClass(
@@ -80,6 +81,7 @@ class IrFactoryImplForJsIC(override val stageController: StageController) : Abst
isValue, isValue,
isExpect, isExpect,
isFun, isFun,
hasEnumEntries,
source, source,
).register() ).register()
} }
@@ -37,6 +37,7 @@ class IrLazyClass(
override var isValue: Boolean, override var isValue: Boolean,
override var isExpect: Boolean, override var isExpect: Boolean,
override var isFun: Boolean, override var isFun: Boolean,
override var hasEnumEntries: Boolean,
override val stubGenerator: DeclarationStubGenerator, override val stubGenerator: DeclarationStubGenerator,
override val typeTranslator: TypeTranslator override val typeTranslator: TypeTranslator
) : IrClass(), IrLazyDeclarationBase, DeserializableClass { ) : IrClass(), IrLazyDeclarationBase, DeserializableClass {
@@ -36,6 +36,7 @@ class LazyIrFactory(
isValue: Boolean, isValue: Boolean,
isExpect: Boolean, isExpect: Boolean,
isFun: Boolean, isFun: Boolean,
hasEnumEntries: Boolean,
source: SourceElement source: SourceElement
): IrClass = if (symbol.isBound) ): IrClass = if (symbol.isBound)
symbol.owner symbol.owner
@@ -56,6 +57,7 @@ class LazyIrFactory(
isValue, isValue,
isExpect, isExpect,
isFun, isFun,
hasEnumEntries,
source, source,
) )
@@ -24,11 +24,14 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.lazy.* import org.jetbrains.kotlin.ir.declarations.lazy.*
import org.jetbrains.kotlin.ir.linkage.IrProvider import org.jetbrains.kotlin.ir.linkage.IrProvider
import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.isValueClass import org.jetbrains.kotlin.resolve.isValueClass
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
@@ -277,6 +280,7 @@ abstract class DeclarationStubGenerator(
isValue = isValueClass(), isValue = isValueClass(),
isExpect = isExpect, isExpect = isExpect,
isFun = isFun, isFun = isFun,
hasEnumEntries = descriptor is DeserializedClassDescriptor && descriptor.hasEnumEntriesMetadataFlag,
stubGenerator = this@DeclarationStubGenerator, stubGenerator = this@DeclarationStubGenerator,
typeTranslator = typeTranslator typeTranslator = typeTranslator
) )
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.ir.util package org.jetbrains.kotlin.ir.util
import org.jetbrains.kotlin.ir.* import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.* import org.jetbrains.kotlin.ir.declarations.impl.*
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
@@ -157,6 +159,7 @@ open class DeepCopyIrTreeWithSymbols(
isValue = declaration.isValue, isValue = declaration.isValue,
isExpect = declaration.isExpect, isExpect = declaration.isExpect,
isFun = declaration.isFun, isFun = declaration.isFun,
hasEnumEntries = declaration.hasEnumEntries,
).apply { ).apply {
transformAnnotations(declaration) transformAnnotations(declaration)
copyTypeParametersFrom(declaration) copyTypeParametersFrom(declaration)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.resolve.isValueClass import org.jetbrains.kotlin.resolve.isValueClass
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
val ParameterDescriptor.indexOrMinusOne: Int val ParameterDescriptor.indexOrMinusOne: Int
@@ -52,5 +53,6 @@ fun IrFactory.createIrClassFromDescriptor(
isValue = descriptor.isValueClass(), isValue = descriptor.isValueClass(),
isExpect = descriptor.isExpect, isExpect = descriptor.isExpect,
isFun = descriptor.isFun, isFun = descriptor.isFun,
hasEnumEntries = descriptor is DeserializedClassDescriptor && descriptor.hasEnumEntriesMetadataFlag,
source = descriptor.source, source = descriptor.source,
) )
@@ -273,6 +273,15 @@ object IrTree : AbstractTreeBuilder() {
+field("isFun", boolean) { +field("isFun", boolean) {
useFieldInIrFactory(defaultValue = false) useFieldInIrFactory(defaultValue = false)
} }
+field("hasEnumEntries", boolean) {
useFieldInIrFactory(defaultValue = false)
kdoc = """
Returns true iff this is a class loaded from dependencies which has the `HAS_ENUM_ENTRIES` metadata flag set.
This flag is useful for Kotlin/JVM to determine whether an enum class from dependency actually has the `entries` property
in its bytecode, as opposed to whether it has it in its member scope, which is true even for enum classes compiled by
old versions of Kotlin which did not support the EnumEntries language feature.
""".trimIndent()
}
+field("source", type<SourceElement>(), mutable = false) { +field("source", type<SourceElement>(), mutable = false) {
useFieldInIrFactory(defaultValue = code("%T.NO_SOURCE", SourceElement::class)) useFieldInIrFactory(defaultValue = code("%T.NO_SOURCE", SourceElement::class))
} }
@@ -104,6 +104,7 @@ internal fun printFactory(generationPath: File, model: Model): GeneratedFile {
parameter("isExpect") parameter("isExpect")
parameter("isFun") parameter("isFun")
parameter("source") parameter("source")
defaultValue["hasEnumEntries"] = false
} }
addDeprecatedFunction(replacement("createConstructor")) { addDeprecatedFunction(replacement("createConstructor")) {
@@ -272,14 +273,11 @@ internal fun printFactory(generationPath: File, model: Model): GeneratedFile {
} }
private class DeprecatedFunctionBuilder(private val replacement: FunSpec) { private class DeprecatedFunctionBuilder(private val replacement: FunSpec) {
val deprecatedFunctionParameterSpecs = mutableListOf<ParameterSpec>() val deprecatedFunctionParameterSpecs = mutableListOf<ParameterSpec>()
var oldName = replacement.name var oldName = replacement.name
var returnType = replacement.returnType var returnType = replacement.returnType
var deprecationMessage: String? = null var deprecationMessage: String? = null
val defaultValue = mutableMapOf<String, Any?>()
fun parameter(name: String, removeDefaultValue: Boolean = false) { fun parameter(name: String, removeDefaultValue: Boolean = false) {
val replacementParameter = val replacementParameter =
@@ -322,6 +320,13 @@ private fun TypeSpec.Builder.addDeprecatedFunction(
indent() indent()
add("%N,\n", parameter) add("%N,\n", parameter)
unindent() unindent()
} else {
val value = builder.defaultValue[parameter.name]
if (value != null) {
indent()
add("$value,\n")
unindent()
}
} }
} }
} }
@@ -374,6 +374,7 @@ class IrDeclarationDeserializer(
isValue = flags.isValue, isValue = flags.isValue,
isExpect = flags.isExpect, isExpect = flags.isExpect,
isFun = flags.isFun, isFun = flags.isFun,
hasEnumEntries = flags.hasEnumEntries,
) )
}.usingParent { }.usingParent {
typeParameters = deserializeTypeParameters(proto.typeParameterList, true) typeParameters = deserializeTypeParameters(proto.typeParameterList, true)
@@ -33,6 +33,7 @@ value class ClassFlags(val flags: Long) {
val isExpect: Boolean get() = IrFlags.IS_EXPECT_CLASS.get(flags.toInt()) val isExpect: Boolean get() = IrFlags.IS_EXPECT_CLASS.get(flags.toInt())
val isExternal: Boolean get() = IrFlags.IS_EXTERNAL_CLASS.get(flags.toInt()) val isExternal: Boolean get() = IrFlags.IS_EXTERNAL_CLASS.get(flags.toInt())
val isFun: Boolean get() = IrFlags.IS_FUN_INTERFACE.get(flags.toInt()) val isFun: Boolean get() = IrFlags.IS_FUN_INTERFACE.get(flags.toInt())
val hasEnumEntries: Boolean get() = IrFlags.HAS_ENUM_ENTRIES.get(flags.toInt())
companion object { companion object {
fun encode(clazz: IrClass, languageVersionSettings: LanguageVersionSettings): Long { fun encode(clazz: IrClass, languageVersionSettings: LanguageVersionSettings): Long {
@@ -52,9 +52,11 @@ class DeserializedClassDescriptor(
VersionRequirementTable.create(classProto.versionRequirementTable), metadataVersion VersionRequirementTable.create(classProto.versionRequirementTable), metadataVersion
) )
val hasEnumEntriesMetadataFlag: Boolean = Flags.HAS_ENUM_ENTRIES.get(classProto.flags)
private val staticScope = private val staticScope =
if (kind == ClassKind.ENUM_CLASS) { if (kind == ClassKind.ENUM_CLASS) {
val enumEntriesCanBeUsed = Flags.HAS_ENUM_ENTRIES.get(classProto.flags) || val enumEntriesCanBeUsed = hasEnumEntriesMetadataFlag ||
c.components.enumEntriesDeserializationSupport.canSynthesizeEnumEntries() == true c.components.enumEntriesDeserializationSupport.canSynthesizeEnumEntries() == true
StaticScopeForKotlinEnum(c.storageManager, this, enumEntriesCanBeUsed) StaticScopeForKotlinEnum(c.storageManager, this, enumEntriesCanBeUsed)
} else { } else {