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 c40c96d2d81..689db92bcd7 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 @@ -380,6 +380,23 @@ interface IrBuilderExtension { ) } + fun buildInitializersRemapping(irClass: IrClass): (IrField) -> IrExpression? { + val original = irClass.constructors.singleOrNull { it.isPrimary } + ?: throw IllegalStateException("Serializable class must have single primary constructor") + // default arguments of original constructor + val defaultsMap: Map = + original.valueParameters.associate { it.descriptor to it.defaultValue?.expression } + return fun(f: IrField): IrExpression? { + val i = f.initializer?.expression ?: return null + return if (i is IrGetValueImpl && i.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) { + // this is a primary constructor property, use corresponding default of value parameter + defaultsMap.getValue(i.descriptor as ParameterDescriptor) + } else { + i + } + } + } + fun findEnumValuesMethod(enumClass: ClassDescriptor): IrFunction { assert(enumClass.kind == ClassKind.ENUM_CLASS) return compilerContext.externalSymbols.referenceClass(enumClass).owner.functions 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 79e76011ee6..9412c7e5761 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 @@ -6,7 +6,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ParameterDescriptor -import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrAnonymousInitializer import org.jetbrains.kotlin.ir.declarations.IrClass @@ -18,7 +17,6 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.TypeTranslator import org.jetbrains.kotlin.ir.util.constructors -import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.descriptorUtil.module @@ -40,19 +38,7 @@ class SerializableIrGenerator( override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) = irClass.contributeConstructor(constructorDescriptor) { ctor -> - val original = irClass.constructors.singleOrNull { it.isPrimary } ?: throw IllegalStateException("Serializable class must have single primary constructor") - // default arguments of original constructor - val defaultsMap: Map = original.valueParameters.associate { it.descriptor to it.defaultValue?.expression } - - fun transformFieldInitializer(f: IrField): IrExpression? { - val i = f.initializer?.expression ?: return null - return if (i is IrGetValueImpl && i.origin == IrStatementOrigin.INITIALIZE_PROPERTY_FROM_PARAMETER) { - // this is a primary constructor property, use corresponding default of value parameter - defaultsMap.getValue(i.descriptor as ParameterDescriptor) - } else { - i - } - } + val transformFieldInitializer = buildInitializersRemapping(irClass) // Missing field exception parts val exceptionCtor = 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 9502771b3c7..2e0d06f9daf 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 @@ -7,6 +7,7 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.ir import org.jetbrains.kotlin.backend.common.BackendContext import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irIfThen import org.jetbrains.kotlin.backend.common.lower.irThrow import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -14,11 +15,8 @@ import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.expressions.IrBlockBody -import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* -import org.jetbrains.kotlin.ir.expressions.mapValueParameters -import org.jetbrains.kotlin.ir.expressions.mapValueParametersIndexed import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* @@ -48,6 +46,8 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext: override val BackendContext.localSymbolTable: SymbolTable get() = _table + private val serializableIrClass = compilerContext.externalSymbols.referenceClass(serializableDescriptor) + override fun generateSerialDesc() { val desc: PropertyDescriptor = generatedSerialDescPropertyDescriptor ?: return val serialDescImplClass = serializerDescriptor @@ -157,6 +157,9 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext: override fun generateSave(function: FunctionDescriptor) = irClass.contributeFunction(function) { saveFunc -> + val fieldInitializer: (SerializableProperty) -> IrExpression? = + buildInitializersRemapping(serializableIrClass.owner).run { { invoke(it.irField) } } + fun irThis(): IrExpression = IrGetValueImpl(startOffset, endOffset, saveFunc.dispatchReceiverParameter!!.symbol) @@ -183,35 +186,51 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext: val serialObjectSymbol = saveFunc.valueParameters[1] val localOutput = irTemporary(call, "output") + fun SerializableProperty.irGet(): IrGetField = irGetField(irGet(serialObjectSymbol), irField) + // internal serialization via virtual calls? - val labeledProperties = orderedProperties.filter { !it.transient } - for (index in labeledProperties.indices) { - val property = labeledProperties[index] - if (property.transient) continue + for ((index, property) in orderedProperties.filter { !it.transient }.withIndex()) { // output.writeXxxElementValue(classDesc, index, value) val sti = getSerialTypeInfo(property) val innerSerial = serializerInstance(this@SerializerIrGenerator, serializableDescriptor, sti.serializer, property.module, property.type, property.genericIndex) - if (innerSerial == null) { + val elementCall = if (innerSerial == null) { val writeFunc = kOutputClass.referenceMethod("${CallingConventions.encode}${sti.elementMethodPrefix}${CallingConventions.elementPostfix}") - +irInvoke( + irInvoke( irGet(localOutput), writeFunc, irGet(localSerialDesc), irInt(index), - irGetField(irGet(serialObjectSymbol), property.irField) + property.irGet() ) } else { val writeFunc = kOutputClass.referenceMethod("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}") - +irInvoke( + irInvoke( irGet(localOutput), writeFunc, irGet(localSerialDesc), irInt(index), innerSerial, - irGetField(irGet(serialObjectSymbol), property.irField) + property.irGet() ) } + + // check for call to .shouldEncodeElementDefault + if (!property.optional) { + // emit call right away + +elementCall + } else { + // emit check: + // if (obj.prop != DEFAULT_VALUE || output.shouldEncodeElementDefault(this.descriptor, i)) + // output.encodeIntElement(this.descriptor, i, obj.prop) + val shouldEncodeFunc = kOutputClass.referenceMethod(CallingConventions.shouldEncodeDefault) + val partA = irNotEquals(property.irGet(), fieldInitializer(property)!!) // todo: props w/o backing fields + val partB = irInvoke(irGet(localOutput), shouldEncodeFunc, irGet(localSerialDesc), irInt(index)) + // Ir infrastructure does not have dedicated symbol for ||, so + // `a || b == if (a) true else b`, see org.jetbrains.kotlin.ir.builders.PrimitivesKt.oror + val condition = irIfThenElse(compilerContext.irBuiltIns.booleanType, partA, irTrue(), partB) + +irIfThen(condition, elementCall) + } } // output.writeEnd(serialClassDesc) @@ -350,7 +369,7 @@ 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 = compilerContext.externalSymbols.referenceClass(serializableDescriptor) + val ctorDesc = serializableIrClass .owner.constructors.single { it.origin == SERIALIZABLE_PLUGIN_ORIGIN } args = listOf(irGet(bitMasks[0])) + args + irNull() ctorDesc.symbol diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt index 1b84f685266..7345208d2d4 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt @@ -16,20 +16,21 @@ package org.jetbrains.kotlinx.serialization.compiler.backend.js -import org.jetbrains.kotlin.descriptors.ClassDescriptor -import org.jetbrains.kotlin.descriptors.ClassKind -import org.jetbrains.kotlin.descriptors.FunctionDescriptor -import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters import org.jetbrains.kotlin.js.translate.reference.ReferenceTranslator import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtPureClassOrObject import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.isTypeParameter +import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializerOrContext +import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.contextSerializerId import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId @@ -171,4 +172,9 @@ internal fun SerializerJsTranslator.createGetKClassExpression(classDescriptor: C JsInvocation( context.namer().kotlin("getKClass"), context.translateQualifiedReference(classDescriptor) - ) \ No newline at end of file + ) + +fun TranslationContext.buildInitializersRemapping(forClass: KtPureClassOrObject): Map = forClass.run { + (bodyPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } + + primaryPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.defaultValue }) +} \ No newline at end of file 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 e0f726eb173..0e29dfe053e 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 @@ -43,10 +43,7 @@ class SerializableJsTranslator( val context: TranslationContext ) : SerializableCodegen(descriptor, context.bindingContext()) { - private val initMap: Map = declaration.run { - (bodyPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } + - primaryPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.defaultValue }) - } + private val initMap: Map = context.buildInitializersRemapping(declaration) override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt index b62574eae4a..a84ec8c73a6 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.js.translate.context.Namer import org.jetbrains.kotlin.js.translate.context.TranslationContext import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor @@ -28,6 +29,7 @@ import org.jetbrains.kotlin.js.translate.general.Translation import org.jetbrains.kotlin.js.translate.utils.JsAstUtils import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils import org.jetbrains.kotlin.js.translate.utils.TranslationUtils +import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPureClassOrObject import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen @@ -123,12 +125,19 @@ class SerializerJsTranslator(descriptor: ClassDescriptor, context.addDeclarationStatement(f.makeStmt()) } + private fun TranslationContext.referenceMethod(clazz: ClassDescriptor, name: String) = + clazz.getFuncDesc(name).single().let { getNameForDescriptor(it) } + override fun generateSave(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx -> val encoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.ENCODER_CLASS) val kOutputClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.STRUCTURE_ENCODER_CLASS) val wBeginFunc = ctx.getNameForDescriptor( encoderClass.getFuncDesc(CallingConventions.begin).single { it.valueParameters.size == 2 }) val serialClassDescRef = JsNameRef(context.getNameForDescriptor(anySerialDescProperty!!), JsThisRef()) + val initializersMap: Map = context.buildInitializersRemapping( + (serializableDescriptor.findPsi() as? KtPureClassOrObject) + ?: throw AssertionError("Serializable descriptor $serializableDescriptor must have source file to build initializers map") + ) // output.writeBegin(desc, []) val typeParams = serializableDescriptor.declaredTypeParameters.mapIndexed { idx, _ -> @@ -143,6 +152,8 @@ class SerializerJsTranslator(descriptor: ClassDescriptor, val localOutputRef = JsNameRef(localOutputName) +JsVars(JsVars.JsVar(localOutputName, call)) + fun SerializableProperty.jsNameRef() = JsNameRef(ctx.getNameForDescriptor(descriptor), objRef) + // todo: internal serialization via virtual calls val labeledProperties = orderedProperties.filter { !it.transient } for (index in labeledProperties.indices) { @@ -151,24 +162,42 @@ class SerializerJsTranslator(descriptor: ClassDescriptor, // output.writeXxxElementValue(classDesc, index, value) val sti = getSerialTypeInfo(property) val innerSerial = serializerInstance(sti.serializer, property.module, property.type, property.genericIndex) - if (innerSerial == null) { + val invocation = if (innerSerial == null) { val writeFunc = kOutputClass.getFuncDesc("${CallingConventions.encode}${sti.elementMethodPrefix}${CallingConventions.elementPostfix}").single() .let { ctx.getNameForDescriptor(it) } - +JsInvocation(JsNameRef(writeFunc, localOutputRef), - serialClassDescRef, - JsIntLiteral(index), - JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt() + JsInvocation( + JsNameRef(writeFunc, localOutputRef), + serialClassDescRef, + JsIntLiteral(index), + property.jsNameRef() + ).makeStmt() } else { val writeFunc = kOutputClass.getFuncDesc("${CallingConventions.encode}${sti.elementMethodPrefix}Serializable${CallingConventions.elementPostfix}").single() .let { ctx.getNameForDescriptor(it) } - +JsInvocation(JsNameRef(writeFunc, localOutputRef), - serialClassDescRef, - JsIntLiteral(index), - innerSerial, - JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt() + JsInvocation( + JsNameRef(writeFunc, localOutputRef), + serialClassDescRef, + JsIntLiteral(index), + innerSerial, + property.jsNameRef() + ).makeStmt() + } + + if (!property.optional) { + +invocation + } else { + val shouldEncodeFunc = ctx.referenceMethod(kOutputClass, CallingConventions.shouldEncodeDefault) + val defaultValue = + initializersMap.getValue(property.descriptor)?.let { Translation.translateAsExpression(it, ctx) } + ?: throw IllegalStateException("Optional property does not have an initializer?") + val partA = JsBinaryOperation(JsBinaryOperator.NEQ, property.jsNameRef(), defaultValue) + val partB = + JsInvocation(JsNameRef(shouldEncodeFunc, localOutputRef), serialClassDescRef, JsIntLiteral(index)) + val cond = JsBinaryOperation(JsBinaryOperator.OR, partA, partB) + +JsIf(cond, invocation) } } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt index eedd8947f68..00482b96c8c 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/NamingConventions.kt @@ -87,6 +87,7 @@ object CallingConventions { const val encode = "encode" const val decodeElementIndex = "decodeElementIndex" const val elementPostfix = "Element" + const val shouldEncodeDefault = "shouldEncodeElementDefault" const val addElement = "addElement" const val addAnnotation = "pushAnnotation"