diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt index 289640f010c..757f144102b 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrBuilderWithPluginContext.kt @@ -18,9 +18,7 @@ import org.jetbrains.kotlin.ir.deepCopyWithVariables import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol -import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.symbols.impl.* import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* @@ -30,7 +28,6 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.jvm.isJvm import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext @@ -65,7 +62,7 @@ interface IrBuilderWithPluginContext { this.returnType = type name = Name.identifier("") visibility = DescriptorVisibilities.LOCAL - origin = SERIALIZABLE_PLUGIN_ORIGIN + origin = SERIALIZATION_PLUGIN_ORIGIN } function.body = DeclarationIrBuilder(compilerContext, function.symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen) @@ -138,7 +135,7 @@ interface IrBuilderWithPluginContext { fun IrClass.contributeAnonymousInitializer(bodyGen: IrBlockBodyBuilder.() -> Unit) { val symbol = IrAnonymousInitializerSymbolImpl(descriptor) - factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZABLE_PLUGIN_ORIGIN, symbol).also { + factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZATION_PLUGIN_ORIGIN, symbol).also { it.parent = this declarations.add(it) it.body = DeclarationIrBuilder(compilerContext, symbol, startOffset, endOffset).irBlockBody(startOffset, endOffset, bodyGen) @@ -299,7 +296,7 @@ interface IrBuilderWithPluginContext { endOffset = propertyParent.endOffset name = propertyName type = propertyType - origin = SERIALIZABLE_PLUGIN_ORIGIN + origin = SERIALIZATION_PLUGIN_ORIGIN isFinal = true this.visibility = DescriptorVisibilities.PRIVATE }.also { it.parent = propertyParent } @@ -309,7 +306,7 @@ interface IrBuilderWithPluginContext { endOffset = propertyParent.endOffset name = propertyName this.isVar = false - origin = SERIALIZABLE_PLUGIN_ORIGIN + origin = SERIALIZATION_PLUGIN_ORIGIN } prop.apply { @@ -321,7 +318,7 @@ interface IrBuilderWithPluginContext { startOffset = propertyParent.startOffset endOffset = propertyParent.endOffset returnType = propertyType - origin = SERIALIZABLE_PLUGIN_ORIGIN + origin = SERIALIZATION_PLUGIN_ORIGIN this.visibility = visibility modality = Modality.FINAL } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt new file mode 100644 index 00000000000..cebff63e5ad --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt @@ -0,0 +1,123 @@ +/* + * 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.kotlinx.serialization.compiler.backend.ir + +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.builders.declarations.addConstructor +import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.companionObject +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.isInlineClass +import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen +import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext +import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames +import org.jetbrains.kotlinx.serialization.compiler.resolve.bitMaskSlotCount +import org.jetbrains.kotlinx.serialization.compiler.resolve.hasCompanionObjectAsSerializer + +/** + * Generates only specific declarations, but NOT their bodies. + * This pass is needed to be able to reference these declarations from other generated bodies + * (e.g. to if we want to reference write$Self() from serialize(), we need to make sure that at least declaration of write$Self is already created. + * + * These functions were usually stubbed from descriptors, but since FIR discourages purely synthetic functions, + * we manually add them here. + */ +class IrPreGenerator( + val irClass: IrClass, + compilerContext: SerializationPluginContext, +) : BaseIrGenerator(irClass, compilerContext) { + private val serialDescriptorSymbol = compilerContext.getClassFromRuntime(SerialEntityNames.SERIAL_DESCRIPTOR_CLASS) + fun generate() { + preGenerateWriteSelfMethodIfNeeded() + preGenerateDeserializationConstructorIfNeeded() + } + + private fun preGenerateWriteSelfMethodIfNeeded() { + if (!irClass.isInternalSerializable) return + val serializerDescriptor = irClass.classSerializer(compilerContext)?.owner ?: return + if (!irClass.shouldHaveSpecificSyntheticMethods { serializerDescriptor.findPluginGeneratedMethod(SerialEntityNames.SAVE) }) return + if (irClass.findWriteSelfMethod() != null) return + val method = irClass.addFunction { + name = SerialEntityNames.WRITE_SELF_NAME + returnType = compilerContext.irBuiltIns.unitType + visibility = DescriptorVisibilities.PUBLIC + modality = Modality.FINAL + origin = SERIALIZATION_PLUGIN_ORIGIN + } + method.apply { + dispatchReceiverParameter = null // function is static + } + + val typeParams = serializerDescriptor.typeParameters.map { + method.addTypeParameter( + it.name.asString(), compilerContext.irBuiltIns.anyNType + ) + } + val typeParamsAsArguments = typeParams.map { it.defaultType } + + // object + method.addValueParameter( + Name.identifier("self"), irClass.typeWith(typeParamsAsArguments), + SERIALIZATION_PLUGIN_ORIGIN + ) + // encoder + method.addValueParameter( + Name.identifier("output"), + compilerContext.getClassFromRuntime(SerialEntityNames.STRUCTURE_ENCODER_CLASS).defaultType, + SERIALIZATION_PLUGIN_ORIGIN + ) + // descriptor + method.addValueParameter( + Name.identifier("serialDesc"), serialDescriptorSymbol.defaultType, + SERIALIZATION_PLUGIN_ORIGIN + ) + // KSerializer + val kSerializerSymbol = compilerContext.getClassFromRuntime(SerialEntityNames.KSERIALIZER_CLASS) + typeParamsAsArguments.forEachIndexed { i, it -> + method.addValueParameter( + Name.identifier("${SerialEntityNames.typeArgPrefix}$i"), + kSerializerSymbol.typeWith(it), + SERIALIZATION_PLUGIN_ORIGIN + ) + } + } + + private fun preGenerateDeserializationConstructorIfNeeded() { + if (!irClass.isInternalSerializable) return + // do not add synthetic deserialization constructor if .deserialize method is customized + if (irClass.hasCompanionObjectAsSerializer && irClass.companionObject()?.findPluginGeneratedMethod(SerialEntityNames.LOAD) == null) return + if (irClass.isValue) return + if (irClass.findSerializableSyntheticConstructor() != null) return + val ctor = irClass.addConstructor { + origin = SERIALIZATION_PLUGIN_ORIGIN + visibility = DescriptorVisibilities.PUBLIC + } + val markerClassSymbol = + compilerContext.getClassFromInternalSerializationPackage(SerialEntityNames.SERIAL_CTOR_MARKER_NAME.asString()) + val serializableProperties = serializablePropertiesForIrBackend(irClass).serializableProperties + val bitMaskSlotsCount = serializableProperties.bitMaskSlotCount() + + repeat(bitMaskSlotsCount) { + ctor.addValueParameter(Name.identifier("seen$it"), compilerContext.irBuiltIns.intType, SERIALIZATION_PLUGIN_ORIGIN) + } + + for (prop in serializableProperties) { + ctor.addValueParameter(prop.name, prop.type.makeNullableIfNotPrimitive(), SERIALIZATION_PLUGIN_ORIGIN) + } + + ctor.addValueParameter(SerialEntityNames.dummyParamName, markerClassSymbol.defaultType, SERIALIZATION_PLUGIN_ORIGIN) + } + + private fun IrType.makeNullableIfNotPrimitive() = + if (this.isPrimitiveType(false)) this + else this.makeNullable() + +} \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt index 2bfe6f32387..d9d5cd1f57f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt @@ -163,7 +163,7 @@ internal fun IrConstructor.lastArgumentIsAnnotationArray(): Boolean { return ((lastArgType as? IrSimpleType)?.arguments?.firstOrNull()?.typeOrNull?.classFqName?.toString() == "kotlin.Annotation") } -fun IrClass.serializableSyntheticConstructor(): IrConstructorSymbol? { // FIXME: remove nullability W/A when FIR plugin will add proper deserialization ctor +fun IrClass.findSerializableSyntheticConstructor(): IrConstructorSymbol? { return declarations.filterIsInstance().singleOrNull { it.isSerializationCtor() }?.symbol } @@ -210,4 +210,7 @@ fun IrType.classOrUpperBound(): IrClassSymbol? = when(val cls = classifierOrNull is IrScriptSymbol -> cls.owner.targetClass is IrTypeParameterSymbol -> cls.owner.representativeUpperBound.classOrUpperBound() else -> null -} \ No newline at end of file +} + +internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) = + !isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt index fbe7d6a1d28..e0fdd6185f0 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerialInfoImplJvmIrGenerator.kt @@ -84,7 +84,7 @@ class SerialInfoImplJvmIrGenerator( generateSimplePropertyWithBackingField(property.descriptor, irClass, Name.identifier("_" + property.name.asString())) val getter = property.getter!! - getter.origin = SERIALIZABLE_PLUGIN_ORIGIN + getter.origin = SERIALIZATION_PLUGIN_ORIGIN // Add JvmName annotation to property getters to force the resulting JVM method name for 'x' be 'x', instead of 'getX', // and to avoid having useless bridges for it generated in BridgeLowering. // Unfortunately, this results in an extra `@JvmName` annotation in the bytecode, but it shouldn't matter very much. @@ -92,7 +92,7 @@ class SerialInfoImplJvmIrGenerator( val field = property.backingField!! field.visibility = DescriptorVisibilities.PRIVATE - field.origin = SERIALIZABLE_PLUGIN_ORIGIN + field.origin = SERIALIZATION_PLUGIN_ORIGIN val parameter = ctor.addValueParameter(property.name.asString(), field.type) ctorBody.statements += IrSetFieldImpl( @@ -200,7 +200,7 @@ class SerialInfoImplJvmIrGenerator( propertyParent.factory.createProperty( propertyParent.startOffset, propertyParent.endOffset, - SERIALIZABLE_PLUGIN_ORIGIN, + SERIALIZATION_PLUGIN_ORIGIN, IrPropertySymbolImpl(propertyDescriptor), name, visibility, @@ -240,7 +240,7 @@ class SerialInfoImplJvmIrGenerator( originProperty.factory.createField( originProperty.startOffset, originProperty.endOffset, - SERIALIZABLE_PLUGIN_ORIGIN, + SERIALIZATION_PLUGIN_ORIGIN, IrFieldSymbolImpl(propertyDescriptor), name, type.toIrType(), @@ -274,7 +274,7 @@ class SerialInfoImplJvmIrGenerator( property.factory.createFunction( fieldSymbol.owner.startOffset, fieldSymbol.owner.endOffset, - SERIALIZABLE_PLUGIN_ORIGIN, IrSimpleFunctionSymbolImpl(descriptor), + SERIALIZATION_PLUGIN_ORIGIN, IrSimpleFunctionSymbolImpl(descriptor), name, visibility, modality, returnType!!.toIrType(), isInline, isEffectivelyExternal(), isTailrec, isSuspend, isOperator, isInfix, isExpect ) @@ -368,7 +368,7 @@ class SerialInfoImplJvmIrGenerator( fun irValueParameter(descriptor: ParameterDescriptor): IrValueParameter = with(descriptor) { @OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend factory.createValueParameter( - function.startOffset, function.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrValueParameterSymbolImpl(this), + function.startOffset, function.endOffset, SERIALIZATION_PLUGIN_ORIGIN, IrValueParameterSymbolImpl(this), name, indexOrMinusOne, type.toIrType(), varargElementType?.toIrType(), isCrossinline, isNoinline, isHidden = false, isAssignable = false ).also { @@ -394,7 +394,7 @@ class SerialInfoImplJvmIrGenerator( val newTypeParameters = descriptor.typeParameters.map { factory.createTypeParameter( startOffset, endOffset, - SERIALIZABLE_PLUGIN_ORIGIN, + SERIALIZATION_PLUGIN_ORIGIN, IrTypeParameterSymbolImpl(it), it.name, it.index, it.isReified, it.variance ).also { typeParameter -> diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt index 6fd0667616c..f433c00ff2e 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializableIrGenerator.kt @@ -270,7 +270,7 @@ class SerializableIrGenerator( propertiesStart: Int ): Int { check(superClass.isInternalSerializable) - val superCtorRef = superClass.serializableSyntheticConstructor()!! + val superCtorRef = superClass.findSerializableSyntheticConstructor() ?: error("Class serializable internally should have special constructor with marker") val superProperties = serializablePropertiesForIrBackend(superClass).serializableProperties val superSlots = superProperties.bitMaskSlotCount() val arguments = allValueParameters.subList(0, superSlots) + @@ -358,10 +358,7 @@ class SerializableIrGenerator( generateSyntheticMethods() } - private inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) = - !isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) - - private fun generateSyntheticInternalConstructor() { // TODO: this doesn't work with OLD FE + private fun generateSyntheticInternalConstructor() { val serializerDescriptor = irClass.classSerializer(compilerContext)?.owner ?: return if (irClass.shouldHaveSpecificSyntheticMethods { serializerDescriptor.findPluginGeneratedMethod(LOAD) }) { val constrDesc = irClass.constructors.find(IrConstructor::isSerializationCtor) ?: return diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt index 16ecceb0074..9a7200004aa 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerForEnumsGenerator.kt @@ -28,7 +28,6 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames class SerializerForEnumsGenerator( irClass: IrClass, compilerContext: SerializationPluginContext, - serialInfoJvmGenerator: SerialInfoImplJvmIrGenerator, ) : SerializerIrGenerator(irClass, compilerContext, null) { override fun generateSave(function: IrSimpleFunction) = addFunctionBody(function) { saveFunc -> fun irThis(): IrExpression = diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt index 77966852738..722557982a5 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt @@ -47,7 +47,7 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.ST import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.STRUCTURE_ENCODER_CLASS import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.UNKNOWN_FIELD_EXC -object SERIALIZABLE_PLUGIN_ORIGIN : IrDeclarationOriginImpl("SERIALIZER", true) +object SERIALIZATION_PLUGIN_ORIGIN : IrDeclarationOriginImpl("KOTLINX_SERIALIZATION", true) internal typealias FunctionWithArgs = Pair> @@ -118,7 +118,7 @@ open class SerializerIrGenerator( val anonymousInit = irClass.run { val symbol = IrAnonymousInitializerSymbolImpl(descriptor) - irClass.factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZABLE_PLUGIN_ORIGIN, symbol).also { + irClass.factory.createAnonymousInitializer(startOffset, endOffset, SERIALIZATION_PLUGIN_ORIGIN, symbol).also { it.parent = this declarations.add(it) } @@ -495,7 +495,7 @@ open class SerializerIrGenerator( ) val typeArgs = (loadFunc.returnType as IrSimpleType).arguments.map { (it as IrTypeProjection).type } - val deserCtor: IrConstructorSymbol? = serializableIrClass.serializableSyntheticConstructor() + val deserCtor: IrConstructorSymbol? = serializableIrClass.findSerializableSyntheticConstructor() if (serializableIrClass.isInternalSerializable && deserCtor != null) { var args: List = serializableProperties.map { serialPropertiesMap.getValue(it.ir).get() } args = bitMasks.map { irGet(it) } + args + irNull() @@ -619,14 +619,12 @@ open class SerializerIrGenerator( irClass: IrClass, context: SerializationPluginContext, metadataPlugin: SerializationDescriptorSerializerPlugin?, - serialInfoJvmGenerator: SerialInfoImplJvmIrGenerator, ) { val serializableDesc = getSerializableClassDescriptorBySerializer(irClass.symbol.descriptor) ?: return val generator = when { serializableDesc.isEnumWithLegacyGeneratedSerializer() -> SerializerForEnumsGenerator( irClass, - context, - serialInfoJvmGenerator + context ) serializableDesc.isInlineClass() -> SerializerForInlineClassGenerator(irClass, context) else -> SerializerIrGenerator(irClass, context, metadataPlugin) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationLoweringExtension.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationLoweringExtension.kt index 0a0ce0d481f..0dae865977c 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationLoweringExtension.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationLoweringExtension.kt @@ -12,19 +12,13 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.runOnFilePostfix import org.jetbrains.kotlin.backend.jvm.ir.fileParent import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.* 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.platform.jvm.isJvm -import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerialInfoImplJvmIrGenerator -import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableCompanionIrGenerator -import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializableIrGenerator -import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializerIrGenerator +import org.jetbrains.kotlinx.serialization.compiler.backend.ir.* import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver import java.util.concurrent.ConcurrentHashMap @@ -52,25 +46,49 @@ class SerializationPluginContext(baseContext: IrPluginContext, val metadataPlugi internal val copiedStaticWriteSelf: MutableMap = ConcurrentHashMap() } +private inline fun IrClass.runPluginSafe(block: () -> Unit) { + try { + block() + } catch (e: Exception) { + throw CompilationException( + "kotlinx.serialization compiler plugin internal error: unable to transform declaration, see cause", + this.fileParent, + this, + e + ) + } +} + private class SerializerClassLowering( baseContext: IrPluginContext, metadataPlugin: SerializationDescriptorSerializerPlugin?, moduleFragment: IrModuleFragment ) : IrElementTransformerVoid(), ClassLoweringPass { val context: SerializationPluginContext = SerializationPluginContext(baseContext, metadataPlugin) - private val serialInfoJvmGenerator = SerialInfoImplJvmIrGenerator(context, moduleFragment).also { context.serialInfoImplJvmIrGenerator = it } + private val serialInfoJvmGenerator = + SerialInfoImplJvmIrGenerator(context, moduleFragment).also { context.serialInfoImplJvmIrGenerator = it } override fun lower(irClass: IrClass) { - try { + irClass.runPluginSafe { SerializableIrGenerator.generate(irClass, context) - SerializerIrGenerator.generate(irClass, context, context.metadataPlugin, serialInfoJvmGenerator) + SerializerIrGenerator.generate(irClass, context, context.metadataPlugin) SerializableCompanionIrGenerator.generate(irClass, context) if (context.platform.isJvm() && KSerializerDescriptorResolver.isSerialInfoImpl(irClass.descriptor)) { serialInfoJvmGenerator.generate(irClass) } - } catch (e: Exception) { - throw CompilationException("kotlinx.serialization compiler plugin internal error: unable to transform declaration, see cause", irClass.fileParent, irClass, e) + } + } +} + +private class SerializerClassPreLowering( + baseContext: IrPluginContext +) : IrElementTransformerVoid(), ClassLoweringPass { + val context: SerializationPluginContext = SerializationPluginContext(baseContext, null) + + override fun lower(irClass: IrClass) { + irClass.runPluginSafe { + IrPreGenerator(irClass, context).generate() } } } @@ -82,8 +100,9 @@ open class SerializationLoweringExtension @JvmOverloads constructor( moduleFragment: IrModuleFragment, pluginContext: IrPluginContext ) { - val serializerClassLowering = SerializerClassLowering(pluginContext, metadataPlugin, moduleFragment) - for (file in moduleFragment.files) - serializerClassLowering.runOnFileInOrder(file) + val pass1 = SerializerClassPreLowering(pluginContext) + val pass2 = SerializerClassLowering(pluginContext, metadataPlugin, moduleFragment) + moduleFragment.files.forEach(pass1::runOnFileInOrder) + moduleFragment.files.forEach(pass2::runOnFileInOrder) } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirBlackBoxTestGenerated.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirBlackBoxTestGenerated.java index b468e957309..1963b39ba4f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirBlackBoxTestGenerated.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirBlackBoxTestGenerated.java @@ -48,4 +48,10 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization public void testMultipleProperties() throws Exception { runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/multipleProperties.kt"); } + + @Test + @TestMetadata("privatePropertiesSerialization.kt") + public void testPrivatePropertiesSerialization() throws Exception { + runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt"); + } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirMembersTestGenerated.java b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirMembersTestGenerated.java index 92dd59a6c2b..d8c54e154e1 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirMembersTestGenerated.java +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/test/org/jetbrains/kotlinx/serialization/SerializationFirMembersTestGenerated.java @@ -47,4 +47,10 @@ public class SerializationFirMembersTestGenerated extends AbstractSerializationF public void testMultipleProperties() throws Exception { runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/multipleProperties.kt"); } + + @Test + @TestMetadata("privatePropertiesSerialization.kt") + public void testPrivatePropertiesSerialization() throws Exception { + runTest("plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt"); + } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.fir.txt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.fir.txt new file mode 100644 index 00000000000..b9fa68c54e2 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.fir.txt @@ -0,0 +1,98 @@ +FILE: privatePropertiesSerialization.kt + @R|kotlinx/serialization/Serializable|() public open class Parent : R|kotlin/Any| { + public constructor(ctor: R|kotlin/Int|): R|Parent| { + super() + } + + private final val ctor: R|kotlin/Int| = R|/ctor| + private get(): R|kotlin/Int| + + private final var body: R|kotlin/String| = String(42) + private get(): R|kotlin/String| + private set(value: R|kotlin/String|): R|kotlin/Unit| + + public final fun checkDeser(c: R|kotlin/Int|, b: R|kotlin/String|): R|kotlin/String| { + when () { + !=(this@R|/Parent|.R|/Parent.ctor|, R|/c|) -> { + ^checkDeser (String(Ctor : ), this@R|/Parent|.R|/Parent.ctor|) + } + } + + when () { + !=(this@R|/Parent|.R|/Parent.body|, R|/b|) -> { + ^checkDeser (String(Body : ), this@R|/Parent|.R|/Parent.body|) + } + } + + ^checkDeser String(OK) + } + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|Parent.Companion| + + } + + public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|Parent|): R|kotlin/Unit| + + public final fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|Parent| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|Parent.$serializer| + + } + + } + @R|kotlinx/serialization/Serializable|() public final class Derived : R|Parent| { + public constructor(): R|Derived| { + super(Int(42)) + } + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|Derived.Companion| + + } + + public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|Derived|): R|kotlin/Unit| + + public final fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|Derived| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|Derived.$serializer| + + } + + } + public final fun test(targetString: R|kotlin/String|): R|kotlin/String| { + lval c: R|Derived| = R|/Derived.Derived|() + lval j: R|kotlinx/serialization/json/Json| = R|kotlinx/serialization/json/Json|( = Json@fun R|kotlinx/serialization/json/JsonBuilder|.(): R|kotlin/Unit| { + this@R|special/anonymous|.R|kotlinx/serialization/json/JsonBuilder.encodeDefaults| = Boolean(true) + } + ) + lval s: R|kotlin/String| = R|/j|.R|kotlinx/serialization/json/Json.encodeToString|(Q|Derived|.R|/Derived.Companion.serializer|(), R|/c|) + when () { + !=(R|/s|, R|/targetString|) -> { + ^test R|/s| + } + } + + lval d: R|Derived| = R|/j|.R|kotlinx/serialization/json/Json.decodeFromString|(Q|Derived|.R|/Derived.Companion.serializer|(), String({"ctor":43,"body":"43"})) + ^test R|/d|.R|/Parent.checkDeser|(Int(43), String(43)) + ^test String(OK) + } + public final fun box(): R|kotlin/String| { + ^box R|/test|(String({"ctor":42,"body":"42"})) + } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt new file mode 100644 index 00000000000..2a53b853283 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/testData/firMembers/privatePropertiesSerialization.kt @@ -0,0 +1,37 @@ +// WITH_STDLIB + +/** + * write$Self and synthetic constructor are required for correct serialization and deserialization of private fields, + * otherwise inaccessible from e.g. Derived.$serializer.serialize(). + * This test verifies that. + */ +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable +open class Parent(private val ctor: Int) { + private var body: String = "42" + + fun checkDeser(c: Int, b: String): String { + if (this.ctor != c) return "Ctor : ${this.ctor}" + if (this.body != b) return "Body : ${this.body}" + return "OK" + } +} + +@Serializable +class Derived: Parent(42) + +fun test(targetString: String): String { + val c = Derived() + val j = Json { encodeDefaults = true } + val s = j.encodeToString(Derived.serializer(), c) + if (s != targetString) return s + val d = j.decodeFromString(Derived.serializer(), """{"ctor":43,"body":"43"}""") + return d.checkDeser(43, "43") + return "OK" +} + +fun box(): String { + return test("""{"ctor":42,"body":"42"}""") +} \ No newline at end of file