Custom-serializable enums for IR

This commit is contained in:
Leonid Startsev
2019-06-24 16:08:49 +03:00
parent bd8133e02c
commit 72f8f2b7b5
6 changed files with 163 additions and 41 deletions
@@ -558,7 +558,7 @@ interface IrBuilderExtension {
serializerClass = serializableDescriptor.getClassFromInternalSerializationPackage("CommonEnumSerializer")
args = kType.toClassDescriptor!!.let { enumDesc ->
listOf(
irString(enumDesc.name.toString()),
irString(enumDesc.serialName()),
irCall(findEnumValuesMethod(enumDesc)),
createArrayOfExpression(
compilerContext.irBuiltIns.stringType,
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2019 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.backend.common.BackendContext
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.properties
import org.jetbrains.kotlin.ir.util.referenceFunction
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
class SerializerForEnumsGenerator(irClass: IrClass, compilerContext: BackendContext, bindingContext: BindingContext) :
SerializerIrGenerator(irClass, compilerContext, bindingContext) {
override fun generateSave(function: FunctionDescriptor) = irClass.contributeFunction(function) { saveFunc ->
fun irThis(): IrExpression =
IrGetValueImpl(startOffset, endOffset, saveFunc.dispatchReceiverParameter!!.symbol)
val encoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.ENCODER_CLASS)
val descriptorGetterSymbol = compilerContext.externalSymbols.referenceFunction(anySerialDescProperty?.getter!!)
val encodeEnum = encoderClass.referenceMethod(CallingConventions.encodeEnum)
val serialDescGetter = irGet(descriptorGetterSymbol.owner.returnType, irThis(), descriptorGetterSymbol)
val ordinalProp = serializableIrClass.properties.single { it.name == Name.identifier("ordinal") }.getter!!
val getOrdinal = irInvoke(irGet(saveFunc.valueParameters[1]), ordinalProp.symbol)
val call = irInvoke(irGet(saveFunc.valueParameters[0]), encodeEnum, serialDescGetter, getOrdinal)
+call
}
override fun generateLoad(function: FunctionDescriptor) = irClass.contributeFunction(function) { loadFunc ->
fun irThis(): IrExpression =
IrGetValueImpl(startOffset, endOffset, loadFunc.dispatchReceiverParameter!!.symbol)
val decoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.DECODER_CLASS)
val descriptorGetterSymbol = compilerContext.externalSymbols.referenceFunction(anySerialDescProperty?.getter!!)
val decode = decoderClass.referenceMethod(CallingConventions.decodeEnum)
val serialDescGetter = irGet(descriptorGetterSymbol.owner.returnType, irThis(), descriptorGetterSymbol)
val valuesF = serializableIrClass.functions.single { it.name == DescriptorUtils.ENUM_VALUES }
val getValues = irInvoke(dispatchReceiver = null, callee = valuesF.symbol)
val arrayGet =
compilerContext.ir.symbols.array.owner.functions.single { it.name == Name.identifier("get") }.symbol
val getValueByOrdinal =
irInvoke(getValues, arrayGet, irInvoke(irGet(loadFunc.valueParameters[0]), decode, serialDescGetter))
+irReturn(getValueByOrdinal)
}
override fun IrBlockBodyBuilder.instantiateNewDescriptor(
serialDescImplClass: ClassDescriptor,
correctThis: IrExpression
): IrExpression {
val serialDescForEnums = serializerDescriptor
.getClassFromInternalSerializationPackage(SerialEntityNames.SERIAL_DESCRIPTOR_FOR_ENUM)
val ctor =
compilerContext.externalSymbols.referenceConstructor(serialDescForEnums.unsubstitutedPrimaryConstructor!!)
return irInvoke(
null, ctor,
irString(serialName),
typeHint = ctor.owner.returnType
)
}
override fun IrBlockBodyBuilder.addElementsContentToDescriptor(
serialDescImplClass: ClassDescriptor,
localDescriptor: IrVariable,
addFunction: IrFunctionSymbol
) {
val enumEntries = serializableDescriptor.enumEntries()
for (entry in enumEntries) {
// regular .serialName() produces fqName here, which is kinda inconvenient for enum entry
val serialName = entry.annotations.serialNameValue ?: entry.name.toString()
val call = irInvoke(
irGet(localDescriptor),
addFunction,
irString(serialName),
irBoolean(false),
typeHint = compilerContext.irBuiltIns.unitType
)
+call
// serialDesc.pushAnnotation(...)
copySerialInfoAnnotationsToDescriptor(
compilerContext.externalSymbols.referenceClass(entry).owner.annotations,
localDescriptor,
serialDescImplClass.referenceMethod(CallingConventions.addAnnotation)
)
}
}
}
@@ -36,22 +36,20 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.UN
// Is creating synthetic origin is a good idea or not?
object SERIALIZABLE_PLUGIN_ORIGIN : IrDeclarationOriginImpl("SERIALIZER")
class SerializerIrGenerator(val irClass: IrClass, override val compilerContext: BackendContext, bindingContext: BindingContext) :
open class SerializerIrGenerator(val irClass: IrClass, final override val compilerContext: BackendContext, bindingContext: BindingContext) :
SerializerCodegen(irClass.descriptor, bindingContext), IrBuilderExtension {
override val translator: TypeTranslator = compilerContext.createTypeTranslator(serializableDescriptor.module)
final override val translator: TypeTranslator = compilerContext.createTypeTranslator(serializableDescriptor.module)
private val _table = SymbolTable()
override val BackendContext.localSymbolTable: SymbolTable
final override val BackendContext.localSymbolTable: SymbolTable
get() = _table
private val serializableIrClass = compilerContext.externalSymbols.referenceClass(serializableDescriptor).owner
protected val serializableIrClass = compilerContext.externalSymbols.referenceClass(serializableDescriptor).owner
override fun generateSerialDesc() {
val desc: PropertyDescriptor = generatedSerialDescPropertyDescriptor ?: return
val serialDescImplClass = serializerDescriptor
.getClassFromInternalSerializationPackage(SERIAL_DESCRIPTOR_CLASS_IMPL)
val serialDescImplConstructor = serialDescImplClass
.unsubstitutedPrimaryConstructor!!
val addFuncS = serialDescImplClass.referenceMethod(CallingConventions.addElement)
@@ -76,36 +74,14 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
initIrBody.parent = irClass
val ctor = irClass.declarations.filterIsInstance<IrConstructor>().find { it.isPrimary }
?: throw AssertionError("Serializer must have primary constructor")
val serialClassDescImplCtor = compilerContext.externalSymbols.referenceConstructor(serialDescImplConstructor)
compilerContext.localSymbolTable.withScope(initIrBody.descriptor) {
initIrBody.body = compilerContext.createIrBuilder(initIrBody.symbol).irBlockBody {
val localDesc = irTemporary(
irInvoke(
null, serialClassDescImplCtor,
irString(serialName), if (isGeneratedSerializer) irGet(thisAsReceiverParameter) else irNull(),
typeHint = serialClassDescImplCtor.owner.returnType
),
instantiateNewDescriptor(serialDescImplClass, irGet(thisAsReceiverParameter)),
nameHint = "serialDesc"
)
fun addFieldCall(prop: SerializableProperty) = irInvoke(
irGet(localDesc),
addFuncS,
irString(prop.name),
irBoolean(prop.optional),
typeHint = compilerContext.irBuiltIns.unitType
)
for (classProp in serializableProperties) {
if (classProp.transient) continue
+addFieldCall(classProp)
// add property annotations
copySerialInfoAnnotationsToDescriptor(
classProp.irField.correspondingProperty?.annotations.orEmpty(),
localDesc,
serialDescImplClass.referenceMethod(CallingConventions.addAnnotation)
)
}
addElementsContentToDescriptor(serialDescImplClass, localDesc, addFuncS)
// add class annotations
copySerialInfoAnnotationsToDescriptor(
serializableIrClass.annotations,
@@ -128,7 +104,46 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
}
}
private fun IrBlockBodyBuilder.copySerialInfoAnnotationsToDescriptor(
protected open fun IrBlockBodyBuilder.instantiateNewDescriptor(
serialDescImplClass: ClassDescriptor,
correctThis: IrExpression
): IrExpression {
val serialDescImplConstructor = serialDescImplClass
.unsubstitutedPrimaryConstructor!!
val serialClassDescImplCtor = compilerContext.externalSymbols.referenceConstructor(serialDescImplConstructor)
return irInvoke(
null, serialClassDescImplCtor,
irString(serialName), if (isGeneratedSerializer) correctThis else irNull(),
typeHint = serialClassDescImplCtor.owner.returnType
)
}
protected open fun IrBlockBodyBuilder.addElementsContentToDescriptor(
serialDescImplClass: ClassDescriptor,
localDescriptor: IrVariable,
addFunction: IrFunctionSymbol
) {
fun addFieldCall(prop: SerializableProperty) = irInvoke(
irGet(localDescriptor),
addFunction,
irString(prop.name),
irBoolean(prop.optional),
typeHint = compilerContext.irBuiltIns.unitType
)
for (classProp in serializableProperties) {
if (classProp.transient) continue
+addFieldCall(classProp)
// add property annotations
copySerialInfoAnnotationsToDescriptor(
classProp.irField.correspondingProperty?.annotations.orEmpty(),
localDescriptor,
serialDescImplClass.referenceMethod(CallingConventions.addAnnotation)
)
}
}
protected fun IrBlockBodyBuilder.copySerialInfoAnnotationsToDescriptor(
annotations: List<IrConstructorCall>,
receiver: IrVariable,
method: IrFunctionSymbol
@@ -182,7 +197,7 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
/* Already implemented in .generateSerialClassDesc ? */
}
fun ClassDescriptor.referenceMethod(methodName: String) =
protected fun ClassDescriptor.referenceMethod(methodName: String) =
getFuncDesc(methodName).single().let { compilerContext.externalSymbols.referenceFunction(it) }
override fun generateSave(function: FunctionDescriptor) = irClass.contributeFunction(function, fromStubs = true) { saveFunc ->
@@ -242,8 +257,8 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
val (writeFunc, args: List<IrExpression>) = if (innerSerial == null) {
val f =
kOutputClass.referenceMethod("${CallingConventions.encode}${sti.elementMethodPrefix}${CallingConventions.elementPostfix}")
val args = mutableListOf(irGet(localSerialDesc), irInt(index))
if (sti.elementMethodPrefix != "Unit") args += property.irGet()
val args: MutableList<IrExpression> = mutableListOf(irGet(localSerialDesc), irInt(index))
if (sti.elementMethodPrefix != "Unit") args.add(property.irGet())
f to args
} else {
val f =
@@ -444,10 +459,13 @@ class SerializerIrGenerator(val irClass: IrClass, override val compilerContext:
context: BackendContext,
bindingContext: BindingContext
) {
if (getSerializableClassDescriptorBySerializer(irClass.descriptor) != null) {
val serializableDesc = getSerializableClassDescriptorBySerializer(irClass.descriptor) ?: return
if (serializableDesc.isSerializableEnum()) {
SerializerForEnumsGenerator(irClass, context, bindingContext).generate()
} else {
SerializerIrGenerator(irClass, context, bindingContext).generate()
irClass.patchDeclarationParents(irClass.parent)
}
irClass.patchDeclarationParents(irClass.parent)
}
}
}
@@ -28,7 +28,7 @@ class SerializerForEnumsTranslator(
NoLookupLocation.FROM_BACKEND
).single()
val ordinalRef = JsNameRef(context.getNameForDescriptor(ordinalProp), JsNameRef(jsFun.parameters[1].name))
val encodeEnumF = ctx.getNameForDescriptor(encoderClass.getFuncDesc("encodeEnum").single())
val encodeEnumF = ctx.getNameForDescriptor(encoderClass.getFuncDesc(CallingConventions.encodeEnum).single())
val call = JsInvocation(JsNameRef(encodeEnumF, JsNameRef(jsFun.parameters[0].name)), serialClassDescRef, ordinalRef)
+call.makeStmt()
}
@@ -36,7 +36,7 @@ class SerializerForEnumsTranslator(
override fun generateLoad(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx ->
val decoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.DECODER_CLASS)
val serialClassDescRef = JsNameRef(context.getNameForDescriptor(anySerialDescProperty!!), JsThisRef())
val decodeEnumF = ctx.getNameForDescriptor(decoderClass.getFuncDesc("decodeEnum").single())
val decodeEnumF = ctx.getNameForDescriptor(decoderClass.getFuncDesc(CallingConventions.decodeEnum).single())
val valuesFunc = DescriptorUtils.getFunctionByName(serializableDescriptor.staticScope, DescriptorUtils.ENUM_VALUES)
val decodeEnumCall = JsInvocation(JsNameRef(decodeEnumF, JsNameRef(jsFun.parameters[0].name)), serialClassDescRef)
val resultCall = JsArrayAccess(JsInvocation(ctx.getInnerNameForDescriptor(valuesFunc).makeRef()), decodeEnumCall)
@@ -22,7 +22,7 @@ class SerializerForEnumsCodegen(
stackSerialClassDesc(null)
load(objVar, serializableAsmType)
invokevirtual(serializableAsmType.internalName, "ordinal", "()I", false)
invokeinterface(encoderType.internalName, "encodeEnum", "(${descType.descriptor}I)V")
invokeinterface(encoderType.internalName, CallingConventions.encodeEnum, "(${descType.descriptor}I)V")
// return
areturn(Type.VOID_TYPE)
}
@@ -36,7 +36,7 @@ class SerializerForEnumsCodegen(
// input.decodeEnum(descriptor)
load(inputVar, decoderType)
stackSerialClassDesc(null)
invokeinterface(decoderType.internalName, "decodeEnum", "(${descType.descriptor})I")
invokeinterface(decoderType.internalName, CallingConventions.decodeEnum, "(${descType.descriptor})I")
// return
aload(serializableAsmType)
areturn(serializableAsmType)
@@ -91,6 +91,8 @@ object CallingConventions {
const val decode = "decode"
const val update = "update"
const val encode = "encode"
const val encodeEnum = "encodeEnum"
const val decodeEnum = "decodeEnum"
const val decodeElementIndex = "decodeElementIndex"
const val elementPostfix = "Element"
const val shouldEncodeDefault = "shouldEncodeElementDefault"