Custom-serializable enums for JS

This commit is contained in:
Leonid Startsev
2019-06-21 18:00:37 +03:00
parent e76ab06029
commit bd8133e02c
5 changed files with 147 additions and 44 deletions
@@ -82,10 +82,6 @@ fun AbstractSerialGenerator.getSerialTypeInfo(property: SerializableProperty): S
)
SerializableInfo(serializer)
}
T.isEnum() -> {
val serializer = property.module.findClassAcrossModuleDependencies(enumSerializerId)
SerializableInfo(serializer)
}
else -> {
val serializer =
findTypeSerializerOrContext(property.module, property.type, property.descriptor.findPsi())
@@ -31,8 +31,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlinx.serialization.compiler.backend.common.*
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.objectSerializerId
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId
import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
internal class JsBlockBuilder {
@@ -147,9 +146,13 @@ internal fun AbstractSerialGenerator.serializerInstance(
return context.serializerObjectGetter(serializerClass)
} else {
var args = when {
serializerClass.isSerializerWhichRequiersKClass() -> listOf(
serializerClass.classId == contextSerializerId || serializerClass.classId == polymorphicSerializerId -> listOf(
ExpressionVisitor.getObjectKClass(context, kType.toClassDescriptor!!)
)
serializerClass.classId == enumSerializerId -> listOf(
ExpressionVisitor.getObjectKClass(context, kType.toClassDescriptor!!),
JsStringLiteral(kType.serialName())
)
serializerClass.classId == objectSerializerId -> listOf(
JsStringLiteral(kType.serialName()),
context.serializerObjectGetter(kType.toClassDescriptor!!)
@@ -0,0 +1,79 @@
/*
* 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.js
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlinx.serialization.compiler.resolve.*
class SerializerForEnumsTranslator(
descriptor: ClassDescriptor,
translator: DeclarationBodyVisitor,
context: TranslationContext
) : SerializerJsTranslator(descriptor, translator, context) {
override fun generateSave(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx ->
val encoderClass = serializerDescriptor.getClassFromSerializationPackage(SerialEntityNames.ENCODER_CLASS)
val serialClassDescRef = JsNameRef(context.getNameForDescriptor(anySerialDescProperty!!), JsThisRef())
val ordinalProp = serializableDescriptor.unsubstitutedMemberScope.getContributedVariables(
Name.identifier("ordinal"),
NoLookupLocation.FROM_BACKEND
).single()
val ordinalRef = JsNameRef(context.getNameForDescriptor(ordinalProp), JsNameRef(jsFun.parameters[1].name))
val encodeEnumF = ctx.getNameForDescriptor(encoderClass.getFuncDesc("encodeEnum").single())
val call = JsInvocation(JsNameRef(encodeEnumF, JsNameRef(jsFun.parameters[0].name)), serialClassDescRef, ordinalRef)
+call.makeStmt()
}
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 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)
+JsReturn(resultCall)
}
override fun instantiateNewDescriptor(
context: TranslationContext,
correctThis: JsExpression,
baseSerialDescImplClass: ClassDescriptor
): JsExpression {
val serialDescForEnums = serializerDescriptor
.getClassFromInternalSerializationPackage(SerialEntityNames.SERIAL_DESCRIPTOR_FOR_ENUM)
val ctor = serialDescForEnums.unsubstitutedPrimaryConstructor!!
return JsNew(
context.getInnerReference(ctor),
listOf(JsStringLiteral(serialName))
)
}
override fun addElementsContentToDescriptor(
context: TranslationContext,
serialDescriptorInThis: JsNameRef,
addElementFunction: FunctionDescriptor,
pushAnnotationFunction: FunctionDescriptor
) {
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 = JsInvocation(
JsNameRef(context.getNameForDescriptor(addElementFunction), serialDescriptorInThis),
JsStringLiteral(serialName)
)
translator.addInitializerStatement(call.makeStmt())
// serialDesc.pushAnnotation(...)
pushAnnotationsInto(entry, pushAnnotationFunction, serialDescriptorInThis)
}
}
}
@@ -41,11 +41,13 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.*
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.SERIAL_DESCRIPTOR_CLASS_IMPL
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames.typeArgPrefix
class SerializerJsTranslator(descriptor: ClassDescriptor,
val translator: DeclarationBodyVisitor,
val context: TranslationContext) : SerializerCodegen(descriptor, context.bindingContext()) {
open class SerializerJsTranslator(
descriptor: ClassDescriptor,
val translator: DeclarationBodyVisitor,
val context: TranslationContext
) : SerializerCodegen(descriptor, context.bindingContext()) {
private fun generateFunction(descriptor: FunctionDescriptor, bodyGen: JsBlockBuilder.(JsFunction, TranslationContext) -> Unit) {
internal fun generateFunction(descriptor: FunctionDescriptor, bodyGen: JsBlockBuilder.(JsFunction, TranslationContext) -> Unit) {
val f = context.buildFunction(descriptor, bodyGen)
translator.addFunction(descriptor, f, null)
}
@@ -54,16 +56,10 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
override fun generateSerialDesc() {
val desc = generatedSerialDescPropertyDescriptor ?: return
val serialDescImplClass = serializerDescriptor
.getClassFromInternalSerializationPackage(SERIAL_DESCRIPTOR_CLASS_IMPL)
val serialDescImplConstructor = serialDescImplClass
.unsubstitutedPrimaryConstructor!!
.getClassFromInternalSerializationPackage(SERIAL_DESCRIPTOR_CLASS_IMPL)
// this.serialDesc = new SerialDescImpl(...)
val correctThis = context.getDispatchReceiver(JsDescriptorUtils.getReceiverParameterForDeclaration(desc.containingDeclaration))
val value = JsNew(
context.getInnerReference(serialDescImplConstructor),
listOf(JsStringLiteral(serialName), if (isGeneratedSerializer) correctThis else JsNullLiteral())
)
val value = instantiateNewDescriptor(context, correctThis, serialDescImplClass)
val assgmnt = TranslationUtils.assignmentToBackingField(context, desc, value)
translator.addInitializerStatement(assgmnt.makeStmt())
@@ -73,23 +69,44 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
val pushClassFunc = serialDescImplClass.getFuncDesc(CallingConventions.addClassAnnotation).single()
val serialClassDescRef = JsNameRef(context.getNameForDescriptor(generatedSerialDescPropertyDescriptor), JsThisRef())
for (prop in serializableProperties) {
if (prop.transient) continue
val call = JsInvocation(
JsNameRef(context.getNameForDescriptor(addFunc), serialClassDescRef),
JsStringLiteral(prop.name),
JsBooleanLiteral(prop.optional)
)
translator.addInitializerStatement(call.makeStmt())
// serialDesc.pushAnnotation(...)
pushAnnotationsInto(prop.descriptor, pushFunc, serialClassDescRef)
}
addElementsContentToDescriptor(context, serialClassDescRef, addFunc, pushFunc)
// push class annotations
pushAnnotationsInto(serializableDescriptor, pushClassFunc, serialClassDescRef)
}
private fun pushAnnotationsInto(annotated: Annotated, pushFunction: DeclarationDescriptor, intoRef: JsNameRef) {
protected open fun instantiateNewDescriptor(
context: TranslationContext,
correctThis: JsExpression,
baseSerialDescImplClass: ClassDescriptor
): JsExpression {
val serialDescImplConstructor = baseSerialDescImplClass.unsubstitutedPrimaryConstructor!!
return JsNew(
context.getInnerReference(serialDescImplConstructor),
listOf(JsStringLiteral(serialName), if (isGeneratedSerializer) correctThis else JsNullLiteral())
)
}
protected open fun addElementsContentToDescriptor(
context: TranslationContext,
serialDescriptorInThis: JsNameRef,
addElementFunction: FunctionDescriptor,
pushAnnotationFunction: FunctionDescriptor
) {
for (prop in serializableProperties) {
if (prop.transient) continue
val call = JsInvocation(
JsNameRef(context.getNameForDescriptor(addElementFunction), serialDescriptorInThis),
JsStringLiteral(prop.name),
JsBooleanLiteral(prop.optional)
)
translator.addInitializerStatement(call.makeStmt())
// serialDesc.pushAnnotation(...)
pushAnnotationsInto(prop.descriptor, pushAnnotationFunction, serialDescriptorInThis)
}
}
protected fun pushAnnotationsInto(annotated: Annotated, pushFunction: DeclarationDescriptor, intoRef: JsNameRef) {
for ((annotationClass , args, _) in annotated.annotationsWithArguments()) {
val argExprs = args.map { arg ->
Translation.translateAsExpression(arg.getArgumentExpression()!!, context)
@@ -107,11 +124,13 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
override fun generateSerializableClassProperty(property: PropertyDescriptor) {
val propDesc = generatedSerialDescPropertyDescriptor ?: return
val propTranslator = DefaultPropertyTranslator(propDesc, context,
translator.getBackingFieldReference(propDesc))
val propTranslator = DefaultPropertyTranslator(
propDesc, context,
translator.getBackingFieldReference(propDesc)
)
val getterDesc = propDesc.getter!!
val getterExpr = context.getFunctionObject(getterDesc)
.apply { propTranslator.generateDefaultGetterFunction(getterDesc, this) }
.apply { propTranslator.generateDefaultGetterFunction(getterDesc, this) }
translator.addProperty(propDesc, getterExpr, null)
}
@@ -131,7 +150,7 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
context.addDeclarationStatement(f.makeStmt())
}
private fun TranslationContext.referenceMethod(clazz: ClassDescriptor, name: String) =
protected fun TranslationContext.referenceMethod(clazz: ClassDescriptor, name: String) =
getNameForDescriptor(clazz.getFuncDesc(name).single())
override fun generateSave(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx ->
@@ -382,9 +401,18 @@ class SerializerJsTranslator(descriptor: ClassDescriptor,
}
companion object {
fun translate(declaration: KtPureClassOrObject, descriptor: ClassDescriptor, translator: DeclarationBodyVisitor, context: TranslationContext) {
if (getSerializableClassDescriptorBySerializer(descriptor) != null)
fun translate(
declaration: KtPureClassOrObject,
descriptor: ClassDescriptor,
translator: DeclarationBodyVisitor,
context: TranslationContext
) {
val serializableDesc = getSerializableClassDescriptorBySerializer(descriptor) ?: return
if (serializableDesc.isSerializableEnum()) {
SerializerForEnumsTranslator(descriptor, translator, context).generate()
} else {
SerializerJsTranslator(descriptor, translator, context).generate()
}
}
}
}
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
@@ -122,10 +121,10 @@ internal fun ClassDescriptor.isSerializableEnum(): Boolean {
if (this.kind != ClassKind.ENUM_CLASS) return false
if (hasSerializableAnnotationWithoutArgs) return true
if (annotations.hasAnySerialAnnotation) return true
// check entries
for (enumEntry in enumEntries()) {
if (enumEntry.annotations.hasAnySerialAnnotation) return true
}
// todo: check entries without 'recursive call in lazy class member scope'
// for (enumEntry in enumEntries()) {
// if (enumEntry.annotations.hasAnySerialAnnotation) return true
// }
return false
}
@@ -190,8 +189,6 @@ internal val ClassDescriptor?.classSerializer: ClassDescriptor?
internal val ClassDescriptor.hasCompanionObjectAsSerializer: Boolean
get() = isSerializableObject || companionObjectDescriptor?.serializerForClass == this.defaultType
internal fun ClassDescriptor.isSerializerWhichRequiersKClass() = classId in setOf(enumSerializerId, contextSerializerId, polymorphicSerializerId)
// returns only user-overriden Serializer
internal val KotlinType.overridenSerializer: KotlinType?
get() {