Support skipping values equals to defaults in output stream for JS and IR backends
This commit is contained in:
+17
@@ -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<ParameterDescriptor, IrExpression?> =
|
||||
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
|
||||
|
||||
+1
-15
@@ -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<ParameterDescriptor, IrExpression?> = 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 =
|
||||
|
||||
+33
-14
@@ -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<IrExpression> = 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
|
||||
|
||||
+11
-5
@@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
fun TranslationContext.buildInitializersRemapping(forClass: KtPureClassOrObject): Map<PropertyDescriptor, KtExpression?> = forClass.run {
|
||||
(bodyPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } +
|
||||
primaryPropertiesDescriptorsMap(bindingContext()).mapValues { it.value.defaultValue })
|
||||
}
|
||||
+1
-4
@@ -43,10 +43,7 @@ class SerializableJsTranslator(
|
||||
val context: TranslationContext
|
||||
) : SerializableCodegen(descriptor, context.bindingContext()) {
|
||||
|
||||
private val initMap: Map<PropertyDescriptor, KtExpression?> = declaration.run {
|
||||
(bodyPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } +
|
||||
primaryPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.defaultValue })
|
||||
}
|
||||
private val initMap: Map<PropertyDescriptor, KtExpression?> = context.buildInitializersRemapping(declaration)
|
||||
|
||||
override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) {
|
||||
|
||||
|
||||
+39
-10
@@ -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<PropertyDescriptor, KtExpression?> = 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user