Support enum serialization in FIR

for all runtime versions, re-enable optimization in IR backend
by correctly determining presence of functions in runtime.
This commit is contained in:
Leonid Startsev
2022-08-15 18:46:53 +02:00
committed by Space
parent 472aca1491
commit 89a8bdc0f6
14 changed files with 291 additions and 32 deletions
@@ -5,6 +5,10 @@ plugins {
id("jps-compatible")
}
repositories {
mavenLocal()
}
dependencies {
compileOnly(intellijCore())
compileOnly(commonDependency("org.jetbrains.intellij.deps:asm-all"))
@@ -48,10 +48,10 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
= compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ARRAY_MASK_FIELD_MISSING_FUNC_NAME)).singleOrNull()
private val enumSerializerFactoryFunc
= compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME)).singleOrNull()
= compilerContext.enumSerializerFactoryFunc
private val markedEnumSerializerFactoryFunc
= compilerContext.referenceFunctions(CallableId(SerializationPackages.internalPackageFqName, SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME)).singleOrNull()
= compilerContext.markedEnumSerializerFactoryFunc
fun useFieldMissingOptimization(): Boolean {
return throwMissedFieldExceptionFunc != null && throwMissedFieldExceptionArrayFunc != null
@@ -535,7 +535,7 @@ abstract class BaseIrGenerator(private val currentClass: IrClass, final override
}
val serializable = serializerClass?.owner?.let { getSerializableClassDescriptorBySerializer(it) }
val serializable = serializerClass?.owner?.let { compilerContext.getSerializableClassDescriptorBySerializer(it) }
requireNotNull(serializerClass)
val ctor = if (serializable?.typeParameters?.isNotEmpty() == true) {
requireNotNull(
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
import org.jetbrains.kotlinx.serialization.compiler.fir.SerializationPluginKey
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
@@ -61,10 +62,7 @@ internal fun IrClass.findPluginGeneratedMethod(name: String): IrSimpleFunction?
}
}
internal fun IrClass.isEnumWithLegacyGeneratedSerializer(): Boolean = isInternallySerializableEnum() && useGeneratedEnumSerializer
internal val IrClass.useGeneratedEnumSerializer: Boolean
get() = true // FIXME This would break if we try to use this new IR compiler with pre-1.4.1 serialization versions. I think we just need to raise min runtime version.
internal fun IrClass.isEnumWithLegacyGeneratedSerializer(context: SerializationPluginContext): Boolean = isInternallySerializableEnum() && !context.runtimeHasEnumSerializerFactoryFunctions
internal val IrClass.isSealedSerializableInterface: Boolean
get() = kind == ClassKind.INTERFACE && modality == Modality.SEALED && hasSerializableOrMetaAnnotation()
@@ -100,9 +98,9 @@ internal val IrClass.isSerialInfoAnnotation: Boolean
|| annotations.hasAnnotation(SerializationAnnotations.inheritableSerialInfoFqName)
|| annotations.hasAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName)
internal val IrClass.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN))
|| isEnumWithLegacyGeneratedSerializer()
internal fun IrClass.shouldHaveGeneratedSerializer(context: SerializationPluginContext): Boolean
= (isInternalSerializable && (modality == Modality.FINAL || modality == Modality.OPEN))
|| isEnumWithLegacyGeneratedSerializer(context)
internal val IrClass.shouldHaveGeneratedMethodsInCompanion: Boolean
get() = this.isSerializableObject || this.isSerializableEnum() || (this.kind == ClassKind.CLASS && hasSerializableOrMetaAnnotation()) || this.isSealedSerializableInterface
@@ -56,7 +56,7 @@ open class SerializerIrGenerator(
compilerContext: SerializationPluginContext,
metadataPlugin: SerializationDescriptorSerializerPlugin?,
) : BaseIrGenerator(irClass, compilerContext) {
protected val serializableIrClass = getSerializableClassDescriptorBySerializer(irClass)!!
protected val serializableIrClass = compilerContext.getSerializableClassDescriptorBySerializer(irClass)!!
protected val serialName: String = serializableIrClass.serialName()
protected val properties = serializablePropertiesForIrBackend(serializableIrClass, metadataPlugin)
@@ -620,13 +620,13 @@ open class SerializerIrGenerator(
context: SerializationPluginContext,
metadataPlugin: SerializationDescriptorSerializerPlugin?,
) {
val serializableDesc = getSerializableClassDescriptorBySerializer(irClass.symbol.descriptor) ?: return
val serializableDesc = context.getSerializableClassDescriptorBySerializer(irClass) ?: return
val generator = when {
serializableDesc.isEnumWithLegacyGeneratedSerializer() -> SerializerForEnumsGenerator(
serializableDesc.isEnumWithLegacyGeneratedSerializer(context) -> SerializerForEnumsGenerator(
irClass,
context
)
serializableDesc.isInlineClass() -> SerializerForInlineClassGenerator(irClass, context)
serializableDesc.isValue -> SerializerForInlineClassGenerator(irClass, context)
else -> SerializerIrGenerator(irClass, context, metadataPlugin)
}
generator.generate()
@@ -117,7 +117,7 @@ fun findTypeSerializer(context: SerializationPluginContext, type: IrType): IrCla
}
fun findEnumTypeSerializer(context: SerializationPluginContext, type: IrType): IrClassSymbol? {
val classSymbol = type.classOrNull?.owner ?: return null
return if (classSymbol.kind == ClassKind.ENUM_CLASS && !classSymbol.isEnumWithLegacyGeneratedSerializer())
return if (classSymbol.kind == ClassKind.ENUM_CLASS && !classSymbol.isEnumWithLegacyGeneratedSerializer(context))
context.referenceClass(enumSerializerId)
else null
}
@@ -130,7 +130,7 @@ internal fun IrClass?.classSerializer(context: SerializationPluginContext): IrCl
// can infer @Poly?
polymorphicSerializerIfApplicableAutomatically(context)?.let { return it }
// default serializable?
if (shouldHaveGeneratedSerializer) {
if (shouldHaveGeneratedSerializer(context)) {
// $serializer nested class
return this.declarations
.filterIsInstance<IrClass>()
@@ -217,7 +217,7 @@ fun BaseIrGenerator.allSealedSerializableSubclassesFor(
}.unzip()
}
internal fun getSerializableClassDescriptorBySerializer(serializer: IrClass): IrClass? {
internal fun SerializationPluginContext.getSerializableClassDescriptorBySerializer(serializer: IrClass): IrClass? {
val serializerForClass = serializer.serializerForClass
if (serializerForClass != null) return serializerForClass.owner
if (serializer.name !in setOf(
@@ -226,7 +226,7 @@ internal fun getSerializableClassDescriptorBySerializer(serializer: IrClass): Ir
)
) return null
val classDescriptor = (serializer.parent as? IrClass) ?: return null
if (!classDescriptor.shouldHaveGeneratedSerializer) return null
if (!classDescriptor.shouldHaveGeneratedSerializer(this)) return null
return classDescriptor
}
@@ -17,9 +17,12 @@ import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
import java.util.concurrent.ConcurrentHashMap
/**
@@ -44,6 +47,22 @@ class SerializationPluginContext(baseContext: IrPluginContext, val metadataPlugi
lateinit var serialInfoImplJvmIrGenerator: SerialInfoImplJvmIrGenerator
internal val copiedStaticWriteSelf: MutableMap<IrSimpleFunction, IrSimpleFunction> = ConcurrentHashMap()
internal val enumSerializerFactoryFunc = baseContext.referenceFunctions(
CallableId(
SerializationPackages.internalPackageFqName,
SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME
)
).singleOrNull()
internal val markedEnumSerializerFactoryFunc = baseContext.referenceFunctions(
CallableId(
SerializationPackages.internalPackageFqName,
SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME
)
).singleOrNull()
val runtimeHasEnumSerializerFactoryFunctions = enumSerializerFactoryFunc != null && markedEnumSerializerFactoryFunc != null
}
private inline fun IrClass.runPluginSafe(block: () -> Unit) {
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
import org.jetbrains.kotlin.fir.moduleData
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.scopes.impl.toConeType
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
@@ -53,6 +54,21 @@ val kSerializerClassId = ClassId(SerializationPackages.packageFqName, SerialEnti
class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGenerationExtension(session) {
internal val runtimeHasEnumSerializerFactory by lazy {
val hasFactory = session.symbolProvider.getTopLevelCallableSymbols(
SerializationPackages.internalPackageFqName,
SerialEntityNames.ENUM_SERIALIZER_FACTORY_FUNC_NAME
).isNotEmpty()
val hasMarkedFactory = session.symbolProvider.getTopLevelCallableSymbols(
SerializationPackages.internalPackageFqName,
SerialEntityNames.MARKED_ENUM_SERIALIZER_FACTORY_FUNC_NAME
).isNotEmpty()
hasFactory && hasMarkedFactory
}
internal val FirClassSymbol<*>.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && isFinalOrOpen()) || (classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotationWithoutArgs && !runtimeHasEnumSerializerFactory)
override fun getNestedClassifiersNames(classSymbol: FirClassSymbol<*>): Set<Name> {
val result = mutableSetOf<Name>()
if (classSymbol.shouldHaveGeneratedMethodsInCompanion && !classSymbol.isSerializableObject)
@@ -156,7 +172,11 @@ class SerializationFirResolveExtension(session: FirSession) : FirDeclarationGene
return listOf(copy.symbol)
}
private fun generateSerializerGetterInCompanion(owner: FirClassSymbol<*>, serializableClassSymbol: FirClassSymbol<*>, callableId: CallableId): FirNamedFunctionSymbol {
private fun generateSerializerGetterInCompanion(
owner: FirClassSymbol<*>,
serializableClassSymbol: FirClassSymbol<*>,
callableId: CallableId
): FirNamedFunctionSymbol {
val f = buildSimpleFunction {
moduleData = session.moduleData
symbol = FirNamedFunctionSymbol(callableId)
@@ -5,22 +5,16 @@
package org.jetbrains.kotlinx.serialization.compiler.fir
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingDeclarationSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.outerClassSymbol
import org.jetbrains.kotlin.fir.declarations.findArgumentByName
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.expressions.classId
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationAnnotations
import org.jetbrains.kotlinx.serialization.compiler.resolve.isSerializableObject
import org.jetbrains.kotlinx.serialization.compiler.resolve.shouldHaveGeneratedMethodsInCompanion
internal val FirClassSymbol<*>.hasSerializableAnnotation
get() = this.annotations.any {
@@ -56,13 +50,7 @@ internal val FirClassSymbol<*>.isInternalSerializable: Boolean
internal fun FirClassSymbol<*>.isSerializableEnum(): Boolean = classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotation
internal fun FirClassSymbol<*>.isInternallySerializableEnum(): Boolean =
classKind == ClassKind.ENUM_CLASS && hasSerializableAnnotationWithoutArgs
internal val FirClassSymbol<*>.shouldHaveGeneratedSerializer: Boolean
get() = (isInternalSerializable && isFinalOrOpen()) || isInternallySerializableEnum()
private fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
internal fun FirClassSymbol<*>.isFinalOrOpen(): Boolean {
val modality = rawStatus.modality
// null means default modality, final
return (modality == null || modality == Modality.FINAL || modality == Modality.OPEN)
@@ -49,6 +49,12 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/defaultProperties.kt");
}
@Test
@TestMetadata("enums.kt")
public void testEnums() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/enums.kt");
}
@Test
@TestMetadata("multipleProperties.kt")
public void testMultipleProperties() throws Exception {
@@ -48,6 +48,12 @@ public class SerializationFirMembersTestGenerated extends AbstractSerializationF
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/defaultProperties.kt");
}
@Test
@TestMetadata("enums.kt")
public void testEnums() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/enums.kt");
}
@Test
@TestMetadata("multipleProperties.kt")
public void testMultipleProperties() throws Exception {
@@ -37,6 +37,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/boxIr/classSerializerAsObject.kt");
}
@Test
@TestMetadata("enumsAreCached.kt")
public void testEnumsAreCached() throws Exception {
runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/boxIr/enumsAreCached.kt");
}
@Test
@TestMetadata("generics.kt")
public void testGenerics() throws Exception {
@@ -0,0 +1,35 @@
// IGNORE_BACKEND_FIR: JVM_IR
// TARGET_BACKEND: JVM_IR
// WITH_STDLIB
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.internal.*
enum class Plain {
A, B
}
@Serializable enum class WithNames {
@SerialName("A") ENTRY1,
@SerialName("B") ENTRY2
}
@Serializable
class Holder(val p: Plain, val w: WithNames)
@OptIn(InternalSerializationApi::class)
fun box(): String {
val cs = (Holder.serializer() as GeneratedSerializer<*>).childSerializers()
val str1 = cs[0].toString()
if (!str1.contains("kotlinx.serialization.internal.EnumSerializer")) return str1
/**
* Serialization 1.4.1+ have runtime factories to create EnumSerializer instead of synthetic $serializer, saving bytecode
* and bringing consistency. After updating the version, uncomment this block.
*/
// val str2 = cs[1].toString()
// if (!str2.contains("kotlinx.serialization.internal.EnumSerializer")) return str2
return "OK"
}
@@ -0,0 +1,132 @@
FILE: enums.kt
public final enum class Plain : R|kotlin/Enum<Plain>| {
private constructor(): R|Plain| {
super<R|kotlin/Enum<Plain>|>()
}
public final static enum entry A: R|Plain|
public final static enum entry B: R|Plain|
public final static fun values(): R|kotlin/Array<Plain>| {
}
public final static fun valueOf(value: R|kotlin/String|): R|Plain| {
}
public final static val entries: R|kotlin/enums/EnumEntries<Plain>|
public get(): R|kotlin/enums/EnumEntries<Plain>|
}
@R|kotlinx/serialization/Serializable|() public final enum class WithNames : R|kotlin/Enum<WithNames>| {
private constructor(): R|WithNames| {
super<R|kotlin/Enum<WithNames>|>()
}
@R|kotlinx/serialization/SerialName|(value = String(A)) public final static enum entry ENTRY1: R|WithNames| = object : R|WithNames| {
private constructor(): R|<anonymous>| {
super<R|WithNames|>()
}
}
@R|kotlinx/serialization/SerialName|(value = String(B)) public final static enum entry ENTRY2: R|WithNames| = object : R|WithNames| {
private constructor(): R|<anonymous>| {
super<R|WithNames|>()
}
}
public final static fun values(): R|kotlin/Array<WithNames>| {
}
public final static fun valueOf(value: R|kotlin/String|): R|WithNames| {
}
public final static val entries: R|kotlin/enums/EnumEntries<WithNames>|
public get(): R|kotlin/enums/EnumEntries<WithNames>|
public final companion object Companion : R|kotlin/Any| {
public final fun serializer(): R|kotlinx/serialization/KSerializer<WithNames>|
private constructor(): R|WithNames.Companion|
}
public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer<WithNames>| {
public final fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|WithNames|): R|kotlin/Unit|
public final fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|WithNames|
public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor|
public get(): R|kotlinx/serialization/descriptors/SerialDescriptor|
public final fun childSerializers(): R|kotlin/Array<kotlinx/serialization/KSerializer<*>>|
private constructor(): R|WithNames.$serializer|
}
}
@R|kotlinx/serialization/Serializable|() public final class Holder : R|kotlin/Any| {
public constructor(p: R|Plain|, w: R|WithNames|): R|Holder| {
super<R|kotlin/Any|>()
}
public final val p: R|Plain| = R|<local>/p|
public get(): R|Plain|
public final val w: R|WithNames| = R|<local>/w|
public get(): R|WithNames|
public final companion object Companion : R|kotlin/Any| {
public final fun serializer(): R|kotlinx/serialization/KSerializer<Holder>|
private constructor(): R|Holder.Companion|
}
public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer<Holder>| {
public final fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|Holder|): R|kotlin/Unit|
public final fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|Holder|
public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor|
public get(): R|kotlinx/serialization/descriptors/SerialDescriptor|
public final fun childSerializers(): R|kotlin/Array<kotlinx/serialization/KSerializer<*>>|
private constructor(): R|Holder.$serializer|
}
}
@R|kotlin/OptIn|(markerClass = vararg(<getClass>(Q|kotlinx/serialization/InternalSerializationApi|))) public final fun testSerializers(): R|kotlin/String| {
lval cs: R|kotlin/Array<kotlinx/serialization/KSerializer<*>>| = (Q|Holder|.R|/Holder.Companion.serializer|() as R|kotlinx/serialization/internal/GeneratedSerializer<*>|).R|SubstitutionOverride<kotlinx/serialization/internal/GeneratedSerializer.childSerializers: R|kotlin/Array<kotlinx/serialization/KSerializer<*>>|>|()
lval str1: R|kotlin/String| = R|<local>/cs|.R|SubstitutionOverride<kotlin/Array.get: R|kotlinx/serialization/KSerializer<*>|>|(Int(0)).R|kotlin/Any.toString|()
when () {
R|<local>/str1|.R|kotlin/text/contains|(String(kotlinx.serialization.internal.EnumSerializer)).R|kotlin/Boolean.not|() -> {
^testSerializers R|<local>/str1|
}
}
^testSerializers String(OK)
}
public final fun testSerialization(previous: R|kotlin/String|): R|kotlin/String| {
when () {
!=(R|<local>/previous|, String(OK)) -> {
^testSerialization R|<local>/previous|
}
}
lval h: R|Holder| = R|/Holder.Holder|(Q|Plain|.R|/Plain.B|, Q|WithNames|.R|/WithNames.ENTRY1|)
lval s: R|kotlin/String| = Q|kotlinx/serialization/json/Json|.R|kotlinx/serialization/json/Json.encodeToString|<R|Holder|>(Q|Holder|.R|/Holder.Companion.serializer|(), R|<local>/h|)
when () {
!=(R|<local>/s|, String({"p":"B","w":"A"})) -> {
^testSerialization R|<local>/s|
}
}
^testSerialization String(OK)
}
public final fun box(): R|kotlin/String| {
^box R|/testSerialization|(R|/testSerializers|())
}
@@ -0,0 +1,45 @@
// WITH_STDLIB
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.internal.*
enum class Plain {
A, B
}
@Serializable enum class WithNames {
@SerialName("A") ENTRY1,
@SerialName("B") ENTRY2
}
@Serializable
class Holder(val p: Plain, val w: WithNames)
@OptIn(InternalSerializationApi::class)
fun testSerializers(): String {
val cs = (Holder.serializer() as GeneratedSerializer<*>).childSerializers()
val str1 = cs[0].toString()
if (!str1.contains("kotlinx.serialization.internal.EnumSerializer")) return str1
/**
* Serialization 1.4.1+ have runtime factories to create EnumSerializer instead of synthetic $serializer, saving bytecode
* and bringing consistency. After updating the version, uncomment this block.
*/
// val str2 = cs[1].toString()
// if (!str2.contains("kotlinx.serialization.internal.EnumSerializer")) return str2
return "OK"
}
fun testSerialization(previous: String): String {
if (previous != "OK") return previous
val h = Holder(Plain.B, WithNames.ENTRY1)
val s = Json.encodeToString(Holder.serializer(), h)
if (s != """{"p":"B","w":"A"}""") return s
return "OK"
}
fun box(): String {
return testSerialization(testSerializers())
}