diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt index 787a2597000..bb4f063a166 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/GeneratorHelpers.kt @@ -17,10 +17,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrTypeParameterImpl import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* -import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol -import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol -import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol -import org.jetbrains.kotlin.ir.symbols.IrValueSymbol +import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.types.typeWith @@ -489,4 +486,7 @@ interface IrBuilderExtension { ) } } + + fun IrClass.serializableSyntheticConstructor(): IrConstructorSymbol = + this.constructors.single { it.origin == SERIALIZABLE_PLUGIN_ORIGIN }.symbol } 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 ffbb0b3fcba..ad3e2dd561f 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 @@ -5,12 +5,15 @@ import org.jetbrains.kotlin.backend.common.lower.irThrow import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrAnonymousInitializer import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.TypeTranslator import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi @@ -43,15 +46,23 @@ class SerializableIrGenerator( val exceptionCtorRef = compilerContext.externalSymbols.referenceConstructor(exceptionCtor) val exceptionType = exceptionCtorRef.owner.returnType - val thiz = irClass.thisReceiver!! - if (KotlinBuiltIns.isAny(irClass.descriptor.getSuperClassOrAny())) - generateAnySuperConstructorCall(toBuilder = this@contributeConstructor) - else - TODO("Serializable classes with inheritance") - val seenVarsOffset = properties.serializableProperties.bitMaskSlotCount() + val serializableProperties = properties.serializableProperties + val seenVarsOffset = serializableProperties.bitMaskSlotCount() val seenVars = (0 until seenVarsOffset).map { ctor.valueParameters[it] } - for ((index, prop) in properties.serializableProperties.withIndex()) { + val thiz = irClass.thisReceiver!! + val superClass = irClass.descriptor.getSuperClassOrAny() + var startPropOffset: Int = 0 + when { + KotlinBuiltIns.isAny(superClass) -> generateAnySuperConstructorCall(toBuilder = this@contributeConstructor) + superClass.isInternalSerializable -> { + startPropOffset = generateSuperSerializableCall(superClass, ctor.valueParameters, seenVarsOffset) + } + else -> generateSuperNonSerializableCall(superClass) + } + + for (index in startPropOffset until serializableProperties.size) { + val prop = serializableProperties[index] val paramRef = ctor.valueParameters[index + seenVarsOffset] // assign this.a = a in else branch val assignParamExpr = irSetField(irGet(thiz), prop.irField, irGet(paramRef)) @@ -78,7 +89,7 @@ class SerializableIrGenerator( } // remaining initializers of variables - val serialDescs = properties.serializableProperties.map { it.descriptor }.toSet() + val serialDescs = serializableProperties.map { it.descriptor }.toSet() irClass.declarations.asSequence() .filterIsInstance() .filter { it.descriptor !in serialDescs } @@ -94,6 +105,44 @@ class SerializableIrGenerator( } } + private fun IrBlockBodyBuilder.generateSuperNonSerializableCall(superClass: ClassDescriptor) { + val suitableCtor = superClass.constructors.singleOrNull { it.valueParameters.size == 0 } + ?: throw IllegalArgumentException("Non-serializable parent of serializable $serializableDescriptor must have no arg constructor") + val ctorRef = compilerContext.externalSymbols.referenceConstructor(suitableCtor) + +IrDelegatingConstructorCallImpl( + startOffset, + endOffset, + compilerContext.irBuiltIns.unitType, + ctorRef, + suitableCtor + ) + } + + // returns offset in serializable properties array + private fun IrBlockBodyBuilder.generateSuperSerializableCall( + superClass: ClassDescriptor, + allValueParameters: List, + propertiesStart: Int + ): Int { + check(superClass.isInternalSerializable) + val superCtorRef = compilerContext.externalSymbols.referenceClass(superClass).owner.serializableSyntheticConstructor() + val superProperties = SerializableProperties(superClass, bindingContext).serializableProperties + val superSlots = superProperties.bitMaskSlotCount() + val arguments = allValueParameters.subList(0, superSlots) + + allValueParameters.subList(propertiesStart, propertiesStart + superProperties.size) + + allValueParameters.last() // SerializationConstructorMarker + val call = IrDelegatingConstructorCallImpl( + startOffset, + endOffset, + compilerContext.irBuiltIns.unitType, + superCtorRef, + superCtorRef.owner.descriptor + ) + arguments.forEachIndexed { index, parameter -> call.putValueArgument(index, irGet(parameter)) } + +call + return superProperties.size + } + override fun generateWriteSelfMethod(methodDescriptor: FunctionDescriptor) { // no-op } 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 3542b9568b1..f4fa290c310 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 @@ -396,10 +396,8 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext: // todo: set properties in external deserialization var args: List = localProps.map { it.get() } val ctor: IrConstructorSymbol = if (serializableDescriptor.isInternalSerializable) { - val ctorDesc = serializableIrClass - .constructors.single { it.origin == SERIALIZABLE_PLUGIN_ORIGIN } args = bitMasks.map { irGet(it) } + args + irNull() - ctorDesc.symbol + serializableIrClass.serializableSyntheticConstructor() } else { compilerContext.externalSymbols.referenceConstructor(serializableDescriptor.unsubstitutedPrimaryConstructor!!) } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt index e032fb1186a..f9cf493f20f 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.js +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.CompilationException import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -27,9 +28,11 @@ import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor import org.jetbrains.kotlin.js.translate.general.Translation +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.TranslationUtils import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPureClassOrObject +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen import org.jetbrains.kotlinx.serialization.compiler.backend.common.anonymousInitializers import org.jetbrains.kotlinx.serialization.compiler.resolve.* @@ -53,15 +56,38 @@ class SerializableJsTranslator( @Suppress("NAME_SHADOWING") val context = context.innerContextWithAliased(serializableDescriptor.thisAsReceiverParameter, thiz) + // use serializationConstructorMarker for passing "this" from inheritors to base class + val markerAsThis = jsFun.parameters.last().name.makeRef() + +JsVars( JsVars.JsVar( thiz.name, - Namer.createObjectWithPrototypeFrom(context.getInnerNameForDescriptor(serializableDescriptor).makeRef()) + JsAstUtils.or( + markerAsThis, + Namer.createObjectWithPrototypeFrom(context.getInnerNameForDescriptor(serializableDescriptor).makeRef()) + ) ) ) - val seenVarsOffset = properties.serializableProperties.bitMaskSlotCount() + val serializableProperties = properties.serializableProperties + val seenVarsOffset = serializableProperties.bitMaskSlotCount() val seenVars = (0 until seenVarsOffset).map { jsFun.parameters[it].name.makeRef() } - for ((index, prop) in properties.serializableProperties.withIndex()) { + val superClass = serializableDescriptor.getSuperClassOrAny() + var startPropOffset: Int = 0 + when { + KotlinBuiltIns.isAny(superClass) -> { /* no=op */ } + superClass.isInternalSerializable -> { + startPropOffset = generateSuperSerializableCall( + superClass, + jsFun.parameters.map { it.name.makeRef() }, + thiz, + seenVarsOffset + ) + } + else -> generateSuperNonSerializableCall(superClass, thiz) + } + + for (index in startPropOffset until serializableProperties.size) { + val prop = serializableProperties[index] val paramRef = jsFun.parameters[index + seenVarsOffset].name.makeRef() // assign this.a = a in else branch val assignParamStmt = TranslationUtils.assignmentToBackingField(context, prop.descriptor, paramRef).makeStmt() @@ -79,7 +105,7 @@ class SerializableJsTranslator( } //transient initializers and init blocks - val serialDescs = properties.serializableProperties.map { it.descriptor } + val serialDescs = serializableProperties.map { it.descriptor } (initMap - serialDescs).forEach { (desc, expr) -> val e = requireNotNull(expr) { "transient without an initializer" } val initExpr = Translation.translateAsExpression(e, context) @@ -96,6 +122,33 @@ class SerializableJsTranslator( context.addDeclarationStatement(f.makeStmt()) } + private fun JsBlockBuilder.generateSuperNonSerializableCall(superClass: ClassDescriptor, thisParameter: JsExpression) { + val suitableCtor = superClass.constructors.singleOrNull { it.valueParameters.size == 0 } + ?: throw IllegalArgumentException("Non-serializable parent of serializable $serializableDescriptor must have no arg constructor") + if (suitableCtor.isPrimary) { + +JsInvocation(Namer.getFunctionCallRef(context.getInnerReference(superClass)), thisParameter).makeStmt() + } else { + +JsAstUtils.assignment(thisParameter, JsInvocation(context.getInnerReference(suitableCtor), thisParameter)).makeStmt() + } + } + + private fun JsBlockBuilder.generateSuperSerializableCall( + superClass: ClassDescriptor, + parameters: List, + thisParameter: JsExpression, + propertiesStart: Int + ): Int { + val constrDesc = KSerializerDescriptorResolver.createLoadConstructorDescriptor(superClass, context.bindingContext()) + val constrRef = context.getInnerNameForDescriptor(constrDesc).makeRef() + val superProperties = SerializableProperties(superClass, bindingContext).serializableProperties + val superSlots = superProperties.bitMaskSlotCount() + val arguments = parameters.subList(0, superSlots) + + parameters.subList(propertiesStart, propertiesStart + superProperties.size) + + thisParameter // SerializationConstructorMarker + +JsAstUtils.assignment(thisParameter, JsInvocation(constrRef, arguments)).makeStmt() + return superProperties.size + } + override fun generateWriteSelfMethod(methodDescriptor: FunctionDescriptor) { // no-op yet } @@ -117,4 +170,4 @@ class SerializableJsTranslator( } } } -} \ No newline at end of file +}