From bd8133e02cafe79a4a9b3bd9b5cbddca40f5281b Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Fri, 21 Jun 2019 18:00:37 +0300 Subject: [PATCH] Custom-serializable enums for JS --- .../compiler/backend/common/TypeUtil.kt | 4 - .../compiler/backend/js/JsCodegenUtil.kt | 9 +- .../js/SerializerForEnumsTranslator.kt | 79 +++++++++++++++++ .../backend/js/SerializerJsTranslator.kt | 88 ++++++++++++------- .../compiler/resolve/KSerializationUtil.kt | 11 +-- 5 files changed, 147 insertions(+), 44 deletions(-) create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerForEnumsTranslator.kt diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt index 7530c4a7af8..c706ec83843 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt @@ -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()) 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 3e5c3710ad2..d259d5a6cc5 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 @@ -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!!) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerForEnumsTranslator.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerForEnumsTranslator.kt new file mode 100644 index 00000000000..03f1d67a847 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerForEnumsTranslator.kt @@ -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) + } + } +} \ No newline at end of file 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 93805b56232..9a5c6e461b0 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 @@ -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() + } } } } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt index d10ede5cf97..48d766f1819 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializationUtil.kt @@ -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() {