From ef11fb585719b4d671f070491d14824b42da9b59 Mon Sep 17 00:00:00 2001 From: Leonid Startsev Date: Mon, 14 Aug 2017 14:18:18 +0300 Subject: [PATCH] Js synthetic generation plugin. Fix for JS frontend resolving Generating serialClassDesc property and save method (w/o virtual calls). JS Serialization: Load function and synthetic constructor User-defined serial annotations support Reference array serialization in JS Reference array serializer now requires KClass instance as first constructor argument Finding enum serializer in common module for support in JS --- .../kotlin-serialization-compiler.iml | 1 + .../src/META-INF/plugin.xml | 1 + .../backend/common/SerializableCodegen.kt | 5 - .../backend/common/SerializerCodegen.kt | 5 +- .../compiler/backend/common/TypeUtil.kt | 137 ++++++++ .../compiler/backend/js/JsCodegenUtil.kt | 84 +++++ .../backend/js/SerializableJsTranslator.kt | 112 +++++++ .../backend/js/SerializerJsTranslator.kt | 316 ++++++++++++++++++ .../compiler/backend/jvm/JVMCodegenUtil.kt | 69 ++-- .../backend/jvm/SerialInfoCodegenImpl.kt | 5 - .../backend/jvm/SerializableCodegenImpl.kt | 26 +- .../backend/jvm/SerializerCodegenImpl.kt | 14 +- .../SerializationComponentRegistrar.kt | 2 + .../extensions/SerializationJsExtension.kt | 32 ++ .../compiler/resolve/KSerializationUtil.kt | 16 +- .../resolve/KSerializerDescriptorResolver.kt | 2 +- 16 files changed, 735 insertions(+), 92 deletions(-) create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt create mode 100644 plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationJsExtension.kt diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/kotlin-serialization-compiler.iml b/plugins/kotlin-serialization/kotlin-serialization-compiler/kotlin-serialization-compiler.iml index 0946ca030d9..42883528a12 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/kotlin-serialization-compiler.iml +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/kotlin-serialization-compiler.iml @@ -12,5 +12,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/META-INF/plugin.xml b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/META-INF/plugin.xml index 79a80811a0d..47067b63923 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/META-INF/plugin.xml +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/META-INF/plugin.xml @@ -9,5 +9,6 @@ + \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt index 028ebf1ae87..d202dfdb6ab 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializableCodegen.kt @@ -25,11 +25,6 @@ import org.jetbrains.kotlin.psi.KtPureClassOrObject import org.jetbrains.kotlin.psi.synthetics.findClassDescriptor import org.jetbrains.kotlin.resolve.BindingContext -/** - * @author Leonid Startsev - * sandwwraith@gmail.com - */ - abstract class SerializableCodegen(declaration: KtPureClassOrObject, private val bindingContext: BindingContext) { protected val serializableDescriptor: ClassDescriptor = declaration.findClassDescriptor(bindingContext) protected val properties = SerializableProperties(serializableDescriptor, bindingContext) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializerCodegen.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializerCodegen.kt index 732adf7bcc6..2d85f0c6314 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializerCodegen.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/SerializerCodegen.kt @@ -45,6 +45,8 @@ abstract class SerializerCodegen(declaration: KtPureClassOrObject, bindingContex generateSerialDesc() } + protected val serialDescPropertyDescriptor = getPropertyToGenerate(serializerDescriptor, KSerializerDescriptorResolver.SERIAL_DESC_FIELD, + serializerDescriptor::checkSerializableClassPropertyResult) protected abstract fun generateSerialDesc() protected abstract fun generateSerializableClassProperty(property: PropertyDescriptor) @@ -54,8 +56,7 @@ abstract class SerializerCodegen(declaration: KtPureClassOrObject, bindingContex protected abstract fun generateLoad(function: FunctionDescriptor) private fun generateSerializableClassPropertyIfNeeded() { - val property = getPropertyToGenerate(serializerDescriptor, KSerializerDescriptorResolver.SERIAL_DESC_FIELD, - serializerDescriptor::checkSerializableClassPropertyResult) + val property = serialDescPropertyDescriptor ?: return generateSerializableClassProperty(property) } 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 new file mode 100644 index 00000000000..436624024a4 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/common/TypeUtil.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.serialization.compiler.backend.common + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName +import org.jetbrains.kotlin.js.descriptorUtils.nameIfStandardType +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny +import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.containsTypeProjectionsInTopLevelArguments +import org.jetbrains.kotlin.types.typeUtil.isBoolean +import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType +import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId +import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.polymorphicSerializerId +import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId +import org.jetbrains.kotlinx.serialization.compiler.resolve.* + +open class SerialTypeInfo( + val property: SerializableProperty, + val elementMethodPrefix: String, + val serializer: ClassDescriptor? = null, + val unit: Boolean = false +) + +fun getSerialTypeInfo(property: SerializableProperty): SerialTypeInfo { + val T = property.type + return when { + T.isPrimitiveNumberType() or T.isBoolean() -> SerialTypeInfo(property, + T.nameIfStandardType.toString().capitalize()) + KotlinBuiltIns.isString(T) -> SerialTypeInfo(property, "String") + KotlinBuiltIns.isUnit(T) -> SerialTypeInfo(property, "Unit", unit = true) + KotlinBuiltIns.isPrimitiveArray(T) -> TODO("primitive arrays are not supported yet") + KotlinBuiltIns.isNonPrimitiveArray(T.toClassDescriptor!!) -> { + val serializer = property.serializer?.toClassDescriptor ?: + property.module.findClassAcrossModuleDependencies(referenceArraySerializerId) + SerialTypeInfo(property, if (property.type.isMarkedNullable) "Nullable" else "", serializer) + } + T.toClassDescriptor?.kind == ClassKind.ENUM_CLASS -> { + val serializer = property.serializer?.toClassDescriptor ?: + property.module.findClassAcrossModuleDependencies(enumSerializerId) + SerialTypeInfo(property, if (property.type.isMarkedNullable) "Nullable" else "", serializer) + } + else -> { + val serializer = findTypeSerializer(property.module, property.type) + SerialTypeInfo(property, if (property.type.isMarkedNullable) "Nullable" else "", serializer) + } + } +} + +fun findTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { + return if (kType.requiresPolymorphism()) findPolymorphicSerializer(module) + else kType.typeSerializer.toClassDescriptor // check for serializer defined on the type + ?: findStandardKotlinTypeSerializer(module, kType) // otherwise see if there is a standard serializer + ?: findEnumTypeSerializer(module, kType) +} + +fun findStandardKotlinTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { + val name = when (kType.getJetTypeFqName(false)) { + "kotlin.Unit" -> "UnitSerializer" + "Z", "kotlin.Boolean" -> "BooleanSerializer" + "B", "kotlin.Byte" -> "ByteSerializer" + "S", "kotlin.Short" -> "ShortSerializer" + "I", "kotlin.Int" -> "IntSerializer" + "J", "kotlin.Long" -> "LongSerializer" + "F", "kotlin.Float" -> "FloatSerializer" + "D", "kotlin.Double" -> "DoubleSerializer" + "C", "kotlin.Char" -> "CharSerializer" + "kotlin.String" -> "StringSerializer" + "kotlin.collections.Collection", "kotlin.collections.List", "kotlin.collections.ArrayList" -> "ArrayListSerializer" + "kotlin.collections.Set", "kotlin.collections.LinkedHashSet" -> "LinkedHashSetSerializer" + "kotlin.collections.HashSet" -> "HashSetSerializer" + "kotlin.collections.Map", "kotlin.collections.LinkedHashMap" -> "LinkedHashMapSerializer" + "kotlin.collections.HashMap" -> "HashMapSerializer" + "kotlin.collections.Map.Entry" -> "MapEntrySerializer" + else -> return null + } + return module.findClassAcrossModuleDependencies(ClassId(internalPackageFqName, Name.identifier(name))) +} + +fun findEnumTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { + val classDescriptor = kType.toClassDescriptor ?: return null + return if (classDescriptor.kind == ClassKind.ENUM_CLASS) module.findClassAcrossModuleDependencies(enumSerializerId) else null +} + +fun KotlinType.requiresPolymorphism(): Boolean { + return this.toClassDescriptor?.getSuperClassNotAny()?.isInternalSerializable == true + || this.toClassDescriptor?.modality == Modality.OPEN + || this.containsTypeProjectionsInTopLevelArguments() // List<*> +} + +fun findPolymorphicSerializer(module: ModuleDescriptor): ClassDescriptor { + return requireNotNull(module.findClassAcrossModuleDependencies(polymorphicSerializerId)) { "Can't locate polymorphic serializer definition" } +} + +fun KtPureClassOrObject.bodyPropertiesDescriptorsMap(bindingContext: BindingContext): Map = declarations + .asSequence() + .filterIsInstance() + .associateBy { (bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it] as? PropertyDescriptor)!! } + +fun KtPureClassOrObject.primaryPropertiesDescriptorsMap(bindingContext: BindingContext): Map = primaryConstructorParameters + .asSequence() + .filter { it.hasValOrVar() } + .associateBy { bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!! } + +fun KtPureClassOrObject.anonymousInitializers() = declarations + .asSequence() + .filterIsInstance() + .mapNotNull { it.body } + .toList() + +fun SerializableProperty.annotationVarsAndDesc(annotationClass: ClassDescriptor): Pair, List> { + val args: List = (this.descriptor.annotations.findAnnotation(annotationClass.fqNameSafe) as? LazyAnnotationDescriptor)?.annotationEntry?.valueArguments.orEmpty() + val consParams = annotationClass.unsubstitutedPrimaryConstructor?.valueParameters.orEmpty() + if (args.size != consParams.size) throw IllegalArgumentException("Can't use arguments with defaults for serializable annotations yet") + return args to consParams +} \ No newline at end of file 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 new file mode 100644 index 00000000000..15cf66f413e --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/JsCodegenUtil.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.serialization.compiler.backend.js + +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.expression.translateAndAliasParameters + +internal class JsBlockBuilder { + val block: JsBlock = JsBlock() + operator fun JsStatement.unaryPlus() { + block.statements.add(this) + } + + val body: List + get() = block.statements +} + +internal fun JsBlockBuilder.jsWhile(condition: JsExpression, body: JsBlockBuilder.() -> Unit, label: JsLabel? = null) { + val b = JsBlockBuilder() + b.body() + val w = JsWhile(condition, b.block) + if (label == null) { + +w + } else { + label.statement = w + +label + } +} + +internal class JsCasesBuilder() { + val caseList: MutableList = mutableListOf() + operator fun JsSwitchMember.unaryPlus() { + caseList.add(this) + } +} + +internal fun JsCasesBuilder.case(condition: JsExpression, body: JsBlockBuilder.() -> Unit) { + val a = JsCase() + a.caseExpression = condition + val b = JsBlockBuilder() + b.body() + a.statements += b.body + +a +} + +internal fun JsCasesBuilder.default(body: JsBlockBuilder.() -> Unit) { + val a = JsDefault() + val b = JsBlockBuilder() + b.body() + a.statements += b.body + +a +} + +internal fun JsBlockBuilder.jsSwitch(condition: JsExpression, cases: JsCasesBuilder.() -> Unit) { + val b = JsCasesBuilder() + b.cases() + val sw = JsSwitch(condition, b.caseList) + +sw +} + +internal fun TranslationContext.buildFunction(descriptor: FunctionDescriptor, bodyGen: JsBlockBuilder.(JsFunction, TranslationContext) -> Unit): JsFunction { + val functionObject = this.getFunctionObject(descriptor) + val innerCtx = this.newDeclaration(descriptor).translateAndAliasParameters(descriptor, functionObject.parameters) + val b = JsBlockBuilder() + b.bodyGen(functionObject, innerCtx) + functionObject.body.statements += b.body + return functionObject +} \ 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 new file mode 100644 index 00000000000..18291a2417c --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializableJsTranslator.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.serialization.compiler.backend.js + +import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.js.backend.ast.* +import org.jetbrains.kotlin.js.translate.context.Namer +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor +import org.jetbrains.kotlin.js.translate.general.Translation +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtPureClassOrObject +import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen +import org.jetbrains.kotlinx.serialization.compiler.backend.common.anonymousInitializers +import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap +import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryPropertiesDescriptorsMap +import org.jetbrains.kotlinx.serialization.compiler.resolve.getClassFromSerializationPackage +import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable + +class SerializableJsTranslator(val declaration: KtPureClassOrObject, + val translator: DeclarationBodyVisitor, + val context: TranslationContext) : SerializableCodegen(declaration, context.bindingContext()) { + + val initMap: Map = declaration.run { + (bodyPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.delegateExpressionOrInitializer } + + primaryPropertiesDescriptorsMap(context.bindingContext()).mapValues { it.value.defaultValue }) + } + + override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) { + + val missingExceptionClassRef = serializableDescriptor.getClassFromSerializationPackage("MissingFieldException") + .let { context.getQualifiedReference(it) } + + val f = context.buildFunction(constructorDescriptor) { jsFun, context -> + val thiz = jsFun.scope.declareName(Namer.ANOTHER_THIS_PARAMETER_NAME).makeRef() + val context = context.innerContextWithAliased(serializableDescriptor, thiz) + + +JsVars(JsVars.JsVar(thiz.name, Namer.createObjectWithPrototypeFrom(context.getQualifiedReference(serializableDescriptor)))) + val seenVar = jsFun.parameters[0].name.makeRef() + for ((index, prop) in properties.serializableProperties.withIndex()) { + val paramRef = jsFun.parameters[index + 1].name.makeRef() + // assign this.a = a in else branch + val assignParamStmt = TranslationUtils.assignmentToBackingField(context, prop.descriptor, paramRef).makeStmt() + + val ifNotSeenStmt: JsStatement = if (prop.optional) { + val initializer = initMap.getValue(prop.descriptor) ?: throw IllegalArgumentException("optional without an initializer") + val initExpr = Translation.translateAsExpression(initializer, context) + TranslationUtils.assignmentToBackingField(context, prop.descriptor, initExpr).makeStmt() + } + else { + JsThrow(JsNew(missingExceptionClassRef, listOf(JsStringLiteral(prop.name)))) + } + // (seen & 1 << i == 0) -- not seen + val notSeenTest = JsAstUtils.equality( + JsBinaryOperation( + JsBinaryOperator.BIT_AND, + seenVar, + JsIntLiteral(1 shl (index % 32)) + ), + JsIntLiteral(0) + ) + +JsIf(notSeenTest, ifNotSeenStmt, assignParamStmt) + } + + //transient initializers and init blocks + val serialDescs = properties.serializableProperties.map { it.descriptor } + (initMap - serialDescs).forEach { desc, expr -> + val e = requireNotNull(expr) {"transient without an initializer"} + val initExpr = Translation.translateAsExpression(e, context) + +TranslationUtils.assignmentToBackingField(context, desc, initExpr).makeStmt() + } + + declaration.anonymousInitializers() + .forEach { Translation.translateAsExpression(it, context, this.block) } + + +JsReturn(thiz) + } + + f.name = context.getInnerNameForDescriptor(constructorDescriptor) + context.addDeclarationStatement(f.makeStmt()) + } + + override fun generateWriteSelfMethod(methodDescriptor: FunctionDescriptor) { + // no-op yet + } + + companion object { + fun translate(declaration: KtPureClassOrObject, descriptor: ClassDescriptor, translator: DeclarationBodyVisitor, context: TranslationContext) { + if (descriptor.isInternalSerializable) + SerializableJsTranslator(declaration, translator, context).generate() + } + } +} \ 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 new file mode 100644 index 00000000000..974f8650bf2 --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/js/SerializerJsTranslator.kt @@ -0,0 +1,316 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.serialization.compiler.backend.js + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.* +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.js.translate.declaration.DefaultPropertyTranslator +import org.jetbrains.kotlin.js.translate.general.Translation +import org.jetbrains.kotlin.js.translate.utils.JsAstUtils +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.shouldBoxReturnValue +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtPureClassOrObject +import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.builtIns +import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen +import org.jetbrains.kotlinx.serialization.compiler.backend.common.annotationVarsAndDesc +import org.jetbrains.kotlinx.serialization.compiler.backend.common.findTypeSerializer +import org.jetbrains.kotlinx.serialization.compiler.backend.common.getSerialTypeInfo +import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.enumSerializerId +import org.jetbrains.kotlinx.serialization.compiler.backend.jvm.referenceArraySerializerId +import org.jetbrains.kotlinx.serialization.compiler.resolve.* + +class SerializerJsTranslator(declaration: KtPureClassOrObject, + val translator: DeclarationBodyVisitor, + val context: TranslationContext) : SerializerCodegen(declaration, context.bindingContext()) { + + private fun generateFunction(descriptor: FunctionDescriptor, bodyGen: JsBlockBuilder.(JsFunction, TranslationContext) -> Unit) { + val f = context.buildFunction(descriptor, bodyGen) + translator.addFunction(descriptor, f, null) + } + + + override fun generateSerialDesc() { + val desc = serialDescPropertyDescriptor ?: return + val serialDescImplClass = serializerDescriptor + .getClassFromInternalSerializationPackage("SerialClassDescImpl") + val serialDescImplConstructor = serialDescImplClass + .unsubstitutedPrimaryConstructor!! + + // this.serialDesc = new SerialDescImpl(...) + val value = JsNew(context.getInnerReference(serialDescImplConstructor), listOf(JsStringLiteral(serialName))) + val assgmnt = TranslationUtils.assignmentToBackingField(context, desc, value) + translator.addInitializerStatement(assgmnt.makeStmt()) + + // adding elements via serialDesc.addElement(...) + val addFunc = serialDescImplClass.getFuncDesc("addElement").single() + val pushFunc = serialDescImplClass.getFuncDesc("pushAnnotation").single() + val serialClassDescRef = JsNameRef(context.getNameForDescriptor(serialDescPropertyDescriptor), JsThisRef()) + + for (prop in orderedProperties) { + if (prop.transient) continue + val call = JsInvocation(JsNameRef(context.getNameForDescriptor(addFunc), serialClassDescRef), JsStringLiteral(prop.name)) + translator.addInitializerStatement(call.makeStmt()) + // serialDesc.pushAnnotation(...) + for (annotationClass in prop.annotations) { + val (args, _) = prop.annotationVarsAndDesc(annotationClass) + val argExprs = args.map { arg -> + Translation.translateAsExpression(arg.getArgumentExpression()!!, context) + } + val classRef = context.getQualifiedReference(annotationClass) + val invok = JsInvocation(JsNameRef(context.getNameForDescriptor(pushFunc), serialClassDescRef), JsNew(classRef, argExprs)) + translator.addInitializerStatement(invok.makeStmt()) + } + } + } + + override fun generateSerializableClassProperty(property: PropertyDescriptor) { + val propDesc = serialDescPropertyDescriptor ?: return + val propTranslator = DefaultPropertyTranslator(propDesc, context, + translator.getBackingFieldReference(propDesc)) + val getterDesc = propDesc.getter!! + val getterExpr = context.getFunctionObject(getterDesc) + .apply { propTranslator.generateDefaultGetterFunction(getterDesc, this) } + translator.addProperty(propDesc, getterExpr, null) + } + + private fun ClassDescriptor.getFuncDesc(funcName: String) = + unsubstitutedMemberScope.getDescriptorsFiltered { it == Name.identifier(funcName) } + + override fun generateSave(function: FunctionDescriptor) = generateFunction(function) { jsFun, ctx -> + val kOutputClass = serializerDescriptor.getClassFromSerializationPackage("KOutput") + val wBeginFunc = ctx.getNameForDescriptor( + kOutputClass.getFuncDesc("writeBegin").single { (it as FunctionDescriptor).valueParameters.size == 2 }) + val serialClassDescRef = JsNameRef(context.getNameForDescriptor(serialDescPropertyDescriptor!!), JsThisRef()) + + // output.writeBegin(desc, []) + val call = JsInvocation(JsNameRef(wBeginFunc, JsNameRef(jsFun.parameters[0].name)), + serialClassDescRef, + JsArrayLiteral()) + val objRef = JsNameRef(jsFun.parameters[1].name) + // output = output.writeBegin... + val localOutputName = jsFun.scope.declareFreshName("output") + val localOutputRef = JsNameRef(localOutputName) + +JsVars(JsVars.JsVar(localOutputName, call)) + + // todo: internal serialization via virtual calls + val labeledProperties = orderedProperties.filter { !it.transient } + for (index in labeledProperties.indices) { + val property = labeledProperties[index] + if (property.transient) continue + // output.writeXxxElementValue(classDesc, index, value) + val sti = getSerialTypeInfo(property) + val innerSerial = if (sti.serializer == null) null else serializerInstance(sti.serializer, property.module, property.type) + if (innerSerial == null) { + val writeFunc = + kOutputClass.getFuncDesc("write${sti.elementMethodPrefix}ElementValue").single() + .let { ctx.getNameForDescriptor(it) } + +JsInvocation(JsNameRef(writeFunc, localOutputRef), + serialClassDescRef, + JsIntLiteral(index), + JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt() + } + else { + val writeFunc = + kOutputClass.getFuncDesc("write${sti.elementMethodPrefix}SerializableElementValue").single() + .let { ctx.getNameForDescriptor(it) } + +JsInvocation(JsNameRef(writeFunc, localOutputRef), + serialClassDescRef, + JsIntLiteral(index), + innerSerial, + JsNameRef(ctx.getNameForDescriptor(property.descriptor), objRef)).makeStmt() + } + } + + // output.writeEnd(serialClassDesc) + val wEndFunc = kOutputClass.getFuncDesc("writeEnd").single() + .let { ctx.getNameForDescriptor(it) } + +JsInvocation(JsNameRef(wEndFunc, localOutputRef), serialClassDescRef).makeStmt() + } + + private fun getQualifiedClassReferenceName(classDescriptor: ClassDescriptor): JsExpression { + return context.getQualifiedReference(classDescriptor) + } + + private fun serializerInstance(serializerClass: ClassDescriptor, module: ModuleDescriptor, kType: KotlinType): JsExpression? { + val nullableSerClass = getQualifiedClassReferenceName( + serializerClass.getClassFromInternalSerializationPackage("NullableSerializer")) + if (serializerClass.kind == ClassKind.OBJECT) { + return getQualifiedClassReferenceName(serializerClass) + } + else { + var args = if (serializerClass.classId == enumSerializerId) + listOf(createGetKClassExpression(kType.toClassDescriptor!!)) + else kType.arguments.map { + val argSer = findTypeSerializer(module, it.type) ?: return null + val expr = serializerInstance(argSer, module, it.type) ?: return null + if (it.type.isMarkedNullable) JsNew(nullableSerClass, listOf(expr)) else expr + } + if (serializerClass.classId == referenceArraySerializerId) + args = listOf(createGetKClassExpression(kType.arguments[0].type.toClassDescriptor!!)) + args + return JsNew(getQualifiedClassReferenceName(serializerClass), args) + } + } + + private fun createGetKClassExpression(classDescriptor: ClassDescriptor): JsExpression = + JsInvocation(context.namer().kotlin("getKClass"), + getQualifiedClassReferenceName(classDescriptor)) + + + override fun generateLoad(function: FunctionDescriptor) = generateFunction(function) { jsFun, context -> + val inputClass = serializerDescriptor.getClassFromSerializationPackage("KInput") + val serialClassDescRef = JsNameRef(context.getNameForDescriptor(serialDescPropertyDescriptor!!), JsThisRef()) + + // var index = -1, readAll = false + val indexVar = JsNameRef(jsFun.scope.declareFreshName("index")) + val readAllVar = JsNameRef(jsFun.scope.declareFreshName("readAll")) + +JsVars(JsVars.JsVar(indexVar.name), JsVars.JsVar(readAllVar.name, JsBooleanLiteral(false))) + + // calculating bit mask vars + val blocksCnt = orderedProperties.size / 32 + 1 + fun bitMaskOff(i: Int) = i / 32 + + // var bitMask0 = 0, bitMask1 = 0... + val bitMasks = (0 until blocksCnt).map { JsNameRef(jsFun.scope.declareFreshName("bitMask$it")) } + +JsVars(bitMasks.map { JsVars.JsVar(it.name, JsIntLiteral(0)) }, false) + + // var localProp0, localProp1, ... + val localProps = orderedProperties.map { JsNameRef(jsFun.scope.declareFreshName(it.name)) } + +JsVars(localProps.map { JsVars.JsVar(it.name) }, true) + + //input = input.readBegin(...) + val inputVar = JsNameRef(jsFun.scope.declareFreshName("input")) + val readBeginF = inputClass.getFuncDesc("readBegin").single() + val call = JsInvocation(JsNameRef(context.getNameForDescriptor(readBeginF), JsNameRef(jsFun.parameters[0].name)), + serialClassDescRef, JsArrayLiteral()) + +JsVars(JsVars.JsVar(inputVar.name, call)) + + // while(true) { + val loop = JsLabel(jsFun.scope.declareFreshName("loopLabel")) + val loopRef = JsNameRef(loop.name) + jsWhile(JsBooleanLiteral(true), { + // index = input.readElement(classDesc) + val readElementF = context.getNameForDescriptor(inputClass.getFuncDesc("readElement").single()) + +JsAstUtils.assignment( + indexVar, + JsInvocation(JsNameRef(readElementF, inputVar), serialClassDescRef) + ).makeStmt() + // switch(index) + jsSwitch (indexVar) { + // -2: readAll = true + case(JsIntLiteral(-2)) { + +JsAstUtils.assignment( + readAllVar, + JsBooleanLiteral(true) + ).makeStmt() + } + // all properties + for ((i, property) in orderedProperties.withIndex()) { + case(JsIntLiteral(i)) { + // input.readXxxElementValue + val sti = getSerialTypeInfo(property) + val innerSerial = if (sti.serializer == null) null else serializerInstance(sti.serializer, property.module, property.type) + val call = if (innerSerial == null) { + val readFunc = + inputClass.getFuncDesc("read${sti.elementMethodPrefix}ElementValue").single() + .let { context.getNameForDescriptor(it) } + JsInvocation(JsNameRef(readFunc, inputVar), + serialClassDescRef, + JsIntLiteral(i)) + } + else { + val readFunc = + inputClass.getFuncDesc("read${sti.elementMethodPrefix}SerializableElementValue").single() + .let { context.getNameForDescriptor(it) } + JsInvocation(JsNameRef(readFunc, inputVar), + serialClassDescRef, + JsIntLiteral(i), + innerSerial) + } + // localPropI = ... + +JsAstUtils.assignment( + localProps[i], + call + ).makeStmt() + // need explicit unit instance + if (sti.unit) { + +JsAstUtils.assignment( + localProps[i], + context.getQualifiedReference(property.type.builtIns.unit) + ).makeStmt() + } + // char unboxing crutch + if (KotlinBuiltIns.isCharOrNullableChar(property.type) && !shouldBoxReturnValue(property.descriptor.getter)) { + +JsAstUtils.assignment( + localProps[i], + Translation.unboxIfNeeded(context, localProps[i], true) + ).makeStmt() + } + + // bitMask[i] |= 1 << x + val bitPos = 1 shl (i % 32) + +JsBinaryOperation(JsBinaryOperator.ASG_BIT_OR, + bitMasks[i / 32], + JsIntLiteral(bitPos) + ).makeStmt() + // if (!readAll) break -- but only if this is last prop + if (i != orderedProperties.lastIndex) + +JsIf(JsAstUtils.not(readAllVar), JsBreak()) + else { + // if (readAll) breakLoop else break + +JsIf(readAllVar, JsBreak(loopRef), JsBreak()) + } + } + } + // case -1, default: break loop + case(JsIntLiteral(-1)) {} + default { + +JsBreak(loopRef) + } + } + }, loop) + + // input.readEnd(desc) + val readEndF = inputClass.getFuncDesc("readEnd").single() + .let { context.getNameForDescriptor(it) } + +JsInvocation( + JsNameRef(readEndF, inputVar), + serialClassDescRef + ).makeStmt() + + // deserialization constructor call + val constrDesc = KSerializerDescriptorResolver.createLoadConstructorDescriptor(serializableDescriptor, context.bindingContext()) + val constrRef = context.getInnerNameForDescriptor(constrDesc).makeRef() + val args: MutableList = mutableListOf(bitMasks[0]) + args += localProps + args += JsNullLiteral() + +JsReturn(JsInvocation(constrRef, args)) + } + + companion object { + fun translate(declaration: KtPureClassOrObject, descriptor: ClassDescriptor, translator: DeclarationBodyVisitor, context: TranslationContext) { + if (getSerializableClassDescriptorBySerializer(descriptor) != null) + SerializerJsTranslator(declaration, translator, context).generate() + } + } +} \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt index 539e866fc99..49fa38c9d80 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/JVMCodegenUtil.kt @@ -21,28 +21,23 @@ import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.load.kotlin.TypeMappingMode import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.descriptorUtil.classId -import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.typeUtil.containsTypeProjectionsInTopLevelArguments +import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerialTypeInfo +import org.jetbrains.kotlinx.serialization.compiler.backend.common.findEnumTypeSerializer +import org.jetbrains.kotlinx.serialization.compiler.backend.common.findPolymorphicSerializer +import org.jetbrains.kotlinx.serialization.compiler.backend.common.requiresPolymorphism import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializableProperty -import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable +import org.jetbrains.kotlinx.serialization.compiler.resolve.internalPackageFqName import org.jetbrains.kotlinx.serialization.compiler.resolve.toClassDescriptor import org.jetbrains.kotlinx.serialization.compiler.resolve.typeSerializer import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter -/** - * @author Leonid Startsev - * sandwwraith@gmail.com - */ - -internal val internalPackageFqName = FqName("kotlinx.serialization.internal") internal val descType = Type.getObjectType("kotlinx/serialization/KSerialClassDesc") internal val descImplType = Type.getObjectType("kotlinx/serialization/internal/SerialClassDescImpl") internal val kOutputType = Type.getObjectType("kotlinx/serialization/KOutput") @@ -84,7 +79,7 @@ fun InstructionAdapter.genKOutputMethodCall(property: SerializableProperty, clas val useSerializer = stackValueSerializerInstance(classCodegen, sti) if (!sti.unit) classCodegen.genPropertyOnStack(this, expressionCodegen.context, property.descriptor, propertyOwnerType, ownerVar) invokevirtual(kOutputType.internalName, - "write" + sti.nn + (if (useSerializer) "Serializable" else "") + "ElementValue", + "write" + sti.elementMethodPrefix + (if (useSerializer) "Serializable" else "") + "ElementValue", "(" + descType.descriptor + "I" + (if (useSerializer) kSerialSaverType.descriptor else "") + (if (sti.unit) "" else sti.type.descriptor) + ")V", false) @@ -121,7 +116,7 @@ internal val polymorphicSerializerId = ClassId(internalPackageFqName, Name.ident internal val referenceArraySerializerId = ClassId(internalPackageFqName, Name.identifier("ReferenceArraySerializer")) // returns false is property should not use serializer -internal fun InstructionAdapter.stackValueSerializerInstance(codegen: ClassBodyCodegen, sti: SerialTypeInfo): Boolean { +internal fun InstructionAdapter.stackValueSerializerInstance(codegen: ClassBodyCodegen, sti: JVMSerialTypeInfo): Boolean { val serializer = sti.serializer ?: return false return stackValueSerializerInstance(codegen, sti.property.module, sti.property.type, serializer, this) } @@ -161,9 +156,10 @@ internal fun stackValueSerializerInstance(codegen: ClassBodyCodegen, module: Mod signature.append(AsmTypes.K_CLASS_TYPE.descriptor) } referenceArraySerializerId -> { - // a special way to instantiate reference array serializer -- need an element java.lang.Class reference + // a special way to instantiate reference array serializer -- need an element KClass reference aconst(codegen.typeMapper.mapType(kType.arguments[0].type, null, TypeMappingMode.GENERIC_ARGUMENT)) - signature.append("Ljava/lang/Class;") + AsmUtil.wrapJavaClassIntoKClass(this) + signature.append(AsmTypes.K_CLASS_TYPE.descriptor) } } // all serializers get arguments with serializers of their generic types @@ -189,19 +185,19 @@ internal fun stackValueSerializerInstance(codegen: ClassBodyCodegen, module: Mod // -class SerialTypeInfo( - val property: SerializableProperty, +class JVMSerialTypeInfo( + property: SerializableProperty, val type: Type, - val nn: String, - val serializer: ClassDescriptor? = null, - val unit: Boolean = false -) + nn: String, + serializer: ClassDescriptor? = null, + unit: Boolean = false +) : SerialTypeInfo(property, nn, serializer, unit) -fun getSerialTypeInfo(property: SerializableProperty, type: Type): SerialTypeInfo { +fun getSerialTypeInfo(property: SerializableProperty, type: Type): JVMSerialTypeInfo { when (type.sort) { BOOLEAN, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, CHAR -> { val name = type.className - return SerialTypeInfo(property, type, Character.toUpperCase(name[0]) + name.substring(1)) + return JVMSerialTypeInfo(property, type, Character.toUpperCase(name[0]) + name.substring(1)) } ARRAY -> { // check for explicit serialization annotation on this property @@ -216,20 +212,20 @@ fun getSerialTypeInfo(property: SerializableProperty, type: Type): SerialTypeInf // primitive elements are not supported yet } } - return SerialTypeInfo(property, Type.getType("Ljava/lang/Object;"), - if (property.type.isMarkedNullable) "Nullable" else "", serializer) + return JVMSerialTypeInfo(property, Type.getType("Ljava/lang/Object;"), + if (property.type.isMarkedNullable) "Nullable" else "", serializer) } OBJECT -> { // no explicit serializer for this property. Check other built in types if (KotlinBuiltIns.isString(property.type)) - return SerialTypeInfo(property, Type.getType("Ljava/lang/String;"), "String") + return JVMSerialTypeInfo(property, Type.getType("Ljava/lang/String;"), "String") if (KotlinBuiltIns.isUnit(property.type)) - return SerialTypeInfo(property, Type.getType("Lkotlin/Unit;"), "Unit", unit = true) + return JVMSerialTypeInfo(property, Type.getType("Lkotlin/Unit;"), "Unit", unit = true) // todo: more efficient enum support here, but only for enums that don't define custom serializer // otherwise, it is a serializer for some other type val serializer = findTypeSerializer(property.module, property.type, type) - return SerialTypeInfo(property, Type.getType("Ljava/lang/Object;"), - if (property.type.isMarkedNullable) "Nullable" else "", serializer) + return JVMSerialTypeInfo(property, Type.getType("Ljava/lang/Object;"), + if (property.type.isMarkedNullable) "Nullable" else "", serializer) } else -> throw AssertionError("Unexpected sort for $type") // should not happen } @@ -239,22 +235,7 @@ fun findTypeSerializer(module: ModuleDescriptor, kType: KotlinType, asmType: Typ return if (kType.requiresPolymorphism()) findPolymorphicSerializer(module) else kType.typeSerializer.toClassDescriptor // check for serializer defined on the type ?: findStandardAsmTypeSerializer(module, asmType) // otherwise see if there is a standard serializer - ?: findStandardKotlinTypeSerializer(module, kType) -} - -fun KotlinType.requiresPolymorphism(): Boolean { - return this.toClassDescriptor?.getSuperClassNotAny()?.isInternalSerializable == true - || this.toClassDescriptor?.modality == Modality.OPEN - || this.containsTypeProjectionsInTopLevelArguments() // List<*> -} - -fun findPolymorphicSerializer(module: ModuleDescriptor): ClassDescriptor { - return requireNotNull(module.findClassAcrossModuleDependencies(polymorphicSerializerId)) { "Can't locate polymorphic serializer definition" } -} - -fun findStandardKotlinTypeSerializer(module: ModuleDescriptor, kType: KotlinType): ClassDescriptor? { - val classDescriptor = kType.constructor.declarationDescriptor as? ClassDescriptor ?: return null - return if (classDescriptor.kind == ClassKind.ENUM_CLASS) module.findClassAcrossModuleDependencies(enumSerializerId) else null + ?: findEnumTypeSerializer(module, kType) } fun findStandardAsmTypeSerializer(module: ModuleDescriptor, asmType: Type): ClassDescriptor? { diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerialInfoCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerialInfoCodegenImpl.kt index cf23f80708b..7714114202d 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerialInfoCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerialInfoCodegenImpl.kt @@ -29,11 +29,6 @@ import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type -/** - * @author Leonid Startsev - * sandwwraith@gmail.com - */ - class SerialInfoCodegenImpl(val codegen: ImplementationBodyCodegen, val thisClass: ClassDescriptor, val bindingContext: BindingContext) { val thisAsmType = codegen.typeMapper.mapClass(thisClass) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt index 13214487fdd..b1e6cf5dd2d 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializableCodegenImpl.kt @@ -23,13 +23,13 @@ import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.psi.KtAnonymousInitializer import org.jetbrains.kotlin.psi.KtParameter -import org.jetbrains.kotlin.psi.KtProperty -import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializableCodegen +import org.jetbrains.kotlinx.serialization.compiler.backend.common.anonymousInitializers +import org.jetbrains.kotlinx.serialization.compiler.backend.common.bodyPropertiesDescriptorsMap +import org.jetbrains.kotlinx.serialization.compiler.backend.common.primaryPropertiesDescriptorsMap import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializableProperties import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializableProperty import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable @@ -37,11 +37,6 @@ import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter -/** - * @author Leonid Startsev - * sandwwraith@gmail.com - */ - class SerializableCodegenImpl( private val classCodegen: ImplementationBodyCodegen, serializableClass: ClassDescriptor @@ -57,15 +52,9 @@ class SerializableCodegenImpl( } } - private val descToProps = classCodegen.myClass.declarations - .asSequence() - .filterIsInstance() - .associateBy { classCodegen.bindingContext[BindingContext.VARIABLE, it]!! } + private val descToProps = classCodegen.myClass.bodyPropertiesDescriptorsMap(classCodegen.bindingContext) - private val paramsToProps: Map = classCodegen.myClass.primaryConstructorParameters - .asSequence() - .filter { it.hasValOrVar() } - .associateBy { classCodegen.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!! } + private val paramsToProps: Map = classCodegen.myClass.primaryPropertiesDescriptorsMap(classCodegen.bindingContext) private fun getProp(prop: SerializableProperty) = descToProps[prop.descriptor] private fun getParam(prop: SerializableProperty) = paramsToProps[prop.descriptor] @@ -167,10 +156,7 @@ class SerializableCodegenImpl( // init blocks // todo: proper order with other initializers - classCodegen.myClass.declarations - .asSequence() - .filterIsInstance() - .mapNotNull { it.body } + classCodegen.myClass.anonymousInitializers() .forEach { exprCodegen.gen(it, Type.VOID_TYPE) } areturn(Type.VOID_TYPE) } diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt index 831ca90ac62..b09058942d8 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/backend/jvm/SerializerCodegenImpl.kt @@ -23,12 +23,10 @@ import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen -import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver -import org.jetbrains.kotlin.psi.ValueArgument -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin -import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyAnnotationDescriptor +import org.jetbrains.kotlinx.serialization.compiler.backend.common.SerializerCodegen +import org.jetbrains.kotlinx.serialization.compiler.backend.common.annotationVarsAndDesc +import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver import org.jetbrains.kotlinx.serialization.compiler.resolve.getSerializableClassDescriptorBySerializer import org.jetbrains.kotlinx.serialization.compiler.resolve.isInternalSerializable import org.jetbrains.org.objectweb.asm.Label @@ -79,9 +77,7 @@ class SerializerCodegenImpl( anew(Type.getObjectType(implType)) dup() val sb = StringBuilder("(") - val args: List = (property.descriptor.annotations.findAnnotation(annotationClass.fqNameSafe) as? LazyAnnotationDescriptor)?.annotationEntry?.valueArguments.orEmpty() - val consParams = annotationClass.unsubstitutedPrimaryConstructor?.valueParameters.orEmpty() - if (args.size != consParams.size) throw IllegalArgumentException("Can't use arguments with defaults for serializable annotations yet") + val (args, consParams) = property.annotationVarsAndDesc(annotationClass) for (i in consParams.indices) { val decl = args[i] val desc = consParams[i] @@ -243,7 +239,7 @@ class SerializerCodegenImpl( val sti = getSerialTypeInfo(property, propertyType) val useSerializer = stackValueSerializerInstance(codegen, sti) invokevirtual(kInputType.internalName, - "read" + sti.nn + (if (useSerializer) "Serializable" else "") + "ElementValue", + "read" + sti.elementMethodPrefix + (if (useSerializer) "Serializable" else "") + "ElementValue", "(" + descType.descriptor + "I" + (if (useSerializer) kSerialLoaderType.descriptor else "") + ")" + (if (sti.unit) "V" else sti.type.descriptor), false) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt index a8840f595dd..89b79b98241 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationComponentRegistrar.kt @@ -20,11 +20,13 @@ import com.intellij.mock.MockProject import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension class SerializationComponentRegistrar : ComponentRegistrar { override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { ExpressionCodegenExtension.registerExtension(project, SerializationCodegenExtension()) SyntheticResolveExtension.registerExtension(project, SerializationResolveExtension()) + JsSyntheticTranslateExtension.registerExtension(project, SerializationJsExtension()) } } \ No newline at end of file diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationJsExtension.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationJsExtension.kt new file mode 100644 index 00000000000..1847bd7062b --- /dev/null +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/extensions/SerializationJsExtension.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlinx.serialization.compiler.extensions + +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.js.translate.context.TranslationContext +import org.jetbrains.kotlin.js.translate.declaration.DeclarationBodyVisitor +import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension +import org.jetbrains.kotlin.psi.KtPureClassOrObject +import org.jetbrains.kotlinx.serialization.compiler.backend.js.SerializableJsTranslator +import org.jetbrains.kotlinx.serialization.compiler.backend.js.SerializerJsTranslator + +class SerializationJsExtension: JsSyntheticTranslateExtension { + override fun generateClassSyntheticParts(declaration: KtPureClassOrObject, descriptor: ClassDescriptor, translator: DeclarationBodyVisitor, context: TranslationContext) { + SerializerJsTranslator.translate(declaration, descriptor, translator, context) + SerializableJsTranslator.translate(declaration, descriptor, translator, context) + } +} \ 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 f8a10646060..0a53f641ecf 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 @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.types.* internal val packageFqName = FqName("kotlinx.serialization") +internal val internalPackageFqName = FqName("kotlinx.serialization.internal") // ---- kotlin.serialization.KSerializer @@ -58,12 +59,12 @@ internal val javaSerializableFqName = javaIOPackageFqName.child(javaSerializable fun isJavaSerializable(type: KotlinType?): Boolean = type != null && KotlinBuiltIns.isConstructedFromGivenClass(type, javaSerializableFqName) -fun ClassDescriptor.getJavaSerializableDescriptor(): ClassDescriptor = - module.findClassAcrossModuleDependencies(ClassId(javaIOPackageFqName, javaSerializableName))!! +fun ClassDescriptor.getJavaSerializableDescriptor(): ClassDescriptor? = + module.findClassAcrossModuleDependencies(ClassId(javaIOPackageFqName, javaSerializableName)) -fun ClassDescriptor.getJavaSerializableType(): SimpleType { - return KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, getJavaSerializableDescriptor(), emptyList()) -} +// null on JS frontend +fun ClassDescriptor.getJavaSerializableType(): SimpleType? = + getJavaSerializableDescriptor()?.let { KotlinTypeFactory.simpleNotNullType(Annotations.EMPTY, it, emptyList()) } // ---- kotlin.serialization.Serializable(with=xxx) @@ -175,6 +176,9 @@ fun ClassDescriptor.getKSerializerConstructorMarker(): ClassDescriptor = module.findClassAcrossModuleDependencies(ClassId(packageFqName, kSerializerConstructorMarkerName))!! fun ClassDescriptor.getClassFromSerializationPackage(classSimpleName: String) = - module.findClassAcrossModuleDependencies(ClassId(packageFqName, Name.identifier(classSimpleName)))!! + requireNotNull(module.findClassAcrossModuleDependencies(ClassId(packageFqName, Name.identifier(classSimpleName)))) {"Can't locate class $classSimpleName"} + +fun ClassDescriptor.getClassFromInternalSerializationPackage(classSimpleName: String) = + requireNotNull(module.findClassAcrossModuleDependencies(ClassId(internalPackageFqName, Name.identifier(classSimpleName)))) {"Can't locate class $classSimpleName"} fun ClassDescriptor.toSimpleType(nullable: Boolean = true) = KotlinTypeFactory.simpleType(Annotations.EMPTY, this.typeConstructor, emptyList(), nullable) diff --git a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt index b7d970cd2c4..1c80daa7829 100644 --- a/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt +++ b/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/KSerializerDescriptorResolver.kt @@ -50,7 +50,7 @@ object KSerializerDescriptorResolver { fun addSerializableSupertypes(classDescriptor: ClassDescriptor, supertypes: MutableList) { if (classDescriptor.isInternalSerializable && supertypes.none(::isJavaSerializable)) { - supertypes.add(classDescriptor.getJavaSerializableType()) + classDescriptor.getJavaSerializableType()?.let { supertypes.add(it) } } }