Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialization: Serializable meta annotation #4583

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
compileOnly(project(":compiler:frontend"))
compileOnly(project(":compiler:backend"))
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:ir.tree.impl"))
compileOnly(project(":js:js.frontend"))
compileOnly(project(":js:js.translator"))
compileOnly(project(":kotlin-util-klib-metadata"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fun AbstractSerialGenerator.findAddOnSerializer(propertyType: KotlinType, module
}

fun KotlinType.isGeneratedSerializableObject() =
toClassDescriptor?.run { kind == ClassKind.OBJECT && hasSerializableAnnotationWithoutArgs } == true
toClassDescriptor?.run { kind == ClassKind.OBJECT && hasSerializableOrMetaAnnotationWithoutArgs } == true

@Suppress("FunctionName", "LocalVariableName")
fun AbstractSerialGenerator.getSerialTypeInfo(property: SerializableProperty): SerialTypeInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ interface IrBuilderExtension {
propertyParent: IrClass,
fieldName: Name = propertyDescriptor.name,
): IrProperty {
val irProperty = propertyParent.searchForDeclaration<IrProperty>(propertyDescriptor) ?: run {
val irProperty = propertyParent.searchForDeclaration(propertyDescriptor) ?: run {
with(propertyDescriptor) {
propertyParent.factory.createProperty(
propertyParent.startOffset, propertyParent.endOffset, SERIALIZABLE_PLUGIN_ORIGIN, IrPropertySymbolImpl(propertyDescriptor),
Expand All @@ -520,6 +520,10 @@ interface IrBuilderExtension {
return irProperty
}

fun IrType.kClassToJClassIfNeeded(): IrType = this

fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression = irExpression

private fun IrClass.generatePropertyBackingFieldIfNeeded(
propertyDescriptor: PropertyDescriptor,
originProperty: IrProperty,
Expand Down Expand Up @@ -568,7 +572,10 @@ interface IrBuilderExtension {
}

irAccessor.body = when (isGetter) {
true -> generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
true -> {
irAccessor.returnType = irAccessor.returnType.kClassToJClassIfNeeded()
generateDefaultGetterBody(descriptor as PropertyGetterDescriptor, irAccessor)
}
false -> generateDefaultSetterBody(descriptor as PropertySetterDescriptor, irAccessor)
}

Expand All @@ -588,16 +595,19 @@ interface IrBuilderExtension {

val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol, property)

val propertyIrType = property.type.toIrType()
irBody.statements.add(
IrReturnImpl(
startOffset, endOffset, compilerContext.irBuiltIns.nothingType,
irAccessor.symbol,
IrGetFieldImpl(
startOffset, endOffset,
irProperty.backingField?.symbol ?: error("Property expected to have backing field"),
property.type.toIrType(),
propertyIrType,
receiver
)
).let {
if (propertyIrType.isKClass()) kClassExprToJClassIfNeeded(startOffset, endOffset, it) else it
}
)
)
return irBody
Expand Down Expand Up @@ -1104,7 +1114,7 @@ interface IrBuilderExtension {
}

fun collectSerialInfoAnnotations(irClass: IrClass): List<IrConstructorCall> {
if (!(irClass.isInterface || irClass.descriptor.hasSerializableAnnotation)) return emptyList()
if (!(irClass.isInterface || irClass.descriptor.hasSerializableOrMetaAnnotation)) return emptyList()
val annotationByFq: MutableMap<FqName, IrConstructorCall> = irClass.annotations.associateBy { it.symbol.owner.parentAsClass.descriptor.fqNameSafe }.toMutableMap()
for (clazz in irClass.getAllSuperclasses()) {
val annotations = clazz.annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,29 @@

package org.jetbrains.kotlinx.serialization.compiler.backend.ir

import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
Expand All @@ -24,15 +36,18 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
// This doesn't support annotation arguments of type KClass and Array<KClass> because the codegen doesn't compute JVM signatures for
// such cases correctly (because inheriting from annotation classes is prohibited in Kotlin).
// Currently it results in an "accidental override" error where a method with return type KClass conflicts with the one with Class.
// TODO: support annotation properties of types KClass<...> and Array<KClass<...>>.
class SerialInfoImplJvmIrGenerator(
private val context: SerializationPluginContext,
private val moduleFragment: IrModuleFragment,
) : IrBuilderExtension {
override val compilerContext: SerializationPluginContext
get() = context

private val jvmNameClass get() = context.referenceClass(DescriptorUtils.JVM_NAME)!!.owner

private val javaLangClass = createClass(createPackage("java.lang"), "Class", ClassKind.CLASS)
private val javaLangType = javaLangClass.starProjectedType

private val implGenerated = mutableSetOf<IrClass>()
private val annotationToImpl = mutableMapOf<IrClass, IrClass>()

Expand Down Expand Up @@ -78,7 +93,7 @@ class SerialInfoImplJvmIrGenerator(
field.visibility = DescriptorVisibilities.PRIVATE
field.origin = SERIALIZABLE_PLUGIN_ORIGIN

val parameter = ctor.addValueParameter(property.name.asString(), getter.returnType)
val parameter = ctor.addValueParameter(property.name.asString(), field.type)
ctorBody.statements += IrSetFieldImpl(
startOffset, endOffset, field.symbol,
IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.symbol),
Expand All @@ -95,4 +110,70 @@ class SerialInfoImplJvmIrGenerator(
).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, name))
}

override fun IrType.kClassToJClassIfNeeded(): IrType = when {
this.isKClass() -> javaLangType
this.isKClassArray() -> compilerContext.irBuiltIns.arrayClass.typeWith(javaLangType)
else -> this
}

override fun kClassExprToJClassIfNeeded(startOffset: Int, endOffset: Int, irExpression: IrExpression): IrExpression {
val getterSymbol = kClassJava.owner.getter!!.symbol
return IrCallImpl(
startOffset, endOffset,
javaLangClass.starProjectedType,
getterSymbol,
typeArgumentsCount = getterSymbol.owner.typeParameters.size,
valueArgumentsCount = 0,
origin = IrStatementOrigin.GET_PROPERTY
).apply {
this.extensionReceiver = irExpression
}
}

private val jvmName: IrClassSymbol = createClass(createPackage("kotlin.jvm"), "JvmName", ClassKind.ANNOTATION_CLASS) { klass ->
klass.addConstructor().apply {
addValueParameter("name", context.irBuiltIns.stringType)
}
}

private val kClassJava: IrPropertySymbol =
IrFactoryImpl.buildProperty {
name = Name.identifier("java")
}.apply {
parent = createClass(createPackage("kotlin.jvm"), "JvmClassMappingKt", ClassKind.CLASS).owner
addGetter().apply {
annotations = listOf(
IrConstructorCallImpl.fromSymbolOwner(jvmName.typeWith(), jvmName.constructors.single()).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, "getJavaClass"))
}
)
addExtensionReceiver(context.irBuiltIns.kClassClass.starProjectedType)
returnType = javaLangClass.starProjectedType
}
}.symbol

private fun IrType.isKClassArray() =
this is IrSimpleType && isArray() && arguments.single().typeOrNull?.isKClass() == true

private fun createPackage(packageName: String): IrPackageFragment =
IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment(
moduleFragment.descriptor,
FqName(packageName)
)

private fun createClass(
irPackage: IrPackageFragment,
shortName: String,
classKind: ClassKind,
block: (IrClass) -> Unit = {}
): IrClassSymbol = IrFactoryImpl.buildClass {
name = Name.identifier(shortName)
kind = classKind
modality = Modality.FINAL
}.apply {
parent = irPackage
createImplicitParameterDeclarationWithWrappedDescriptor()
block(this)
}.symbol
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,21 @@ class SerializableIrGenerator(

private val addElementFun = serialDescImplClass.referenceFunctionSymbol(CallingConventions.addElement)

private fun IrClass.hasSerializableAnnotationWithoutArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName) ?: return false

for (i in 0 until annot.valueArgumentsCount) {
if (annot.getValueArgument(i) != null) return false
private fun IrClass.hasSerializableOrMetaAnnotationWithoutArgs(): Boolean {
val annot = getAnnotation(SerializationAnnotations.serializableAnnotationFqName)
if (annot != null) {
for (i in 0 until annot.valueArgumentsCount) {
if (annot.getValueArgument(i) != null) return false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializable annotation always have one argument so I think we may skip a loop here

}
return true
}

return true
val metaAnnotation = annotations
.flatMap { it.symbol.owner.constructedClass.annotations }
.find { it.isAnnotation(SerializationAnnotations.metaSerializableAnnotationFqName) }
return metaAnnotation != null
}

private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableAnnotationWithoutArgs()
private val IrClass.isInternalSerializable: Boolean get() = kind == ClassKind.CLASS && hasSerializableOrMetaAnnotationWithoutArgs()

override fun generateInternalConstructor(constructorDescriptor: ClassConstructorDescriptor) =
irClass.contributeConstructor(constructorDescriptor) { ctor ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ open class SerializerCodegenImpl(
metadataPlugin: SerializationDescriptorSerializerPlugin?
) : SerializerCodegen(codegen.descriptor, codegen.bindingContext, metadataPlugin) {


private val serialDescField = "\$\$serialDesc"

protected val serializerAsmType = codegen.typeMapper.mapClass(codegen.descriptor)
Expand Down
Loading