Rework SerialInfo$Impl annotation implementation to be available in FIR:

- IR plugin does not use it anymore,
regular annotation instantiation feature is used
(insertion of IrConstructorCall(annotationCtorSymbol)).

- IR plugin still generates `SerialInfo$Impl` class to be compatible
with previously compiled code & libraries.

- Custom implementation over descriptors in IR plugin is removed;
plugin now delegates to the existing lowering with some tweaks.

- $Impl descriptor is removed from FE 1.0 because it is no longer needed
for IR plugin; it shouldn't be exposed to users so FIR doesn't need it either.

- Since language lowering is now used, it is possible to correctly instantiate
SerialInfo annotations with default values even if they are from other modules
and even if they were not processed by the plugin.

#KT-48733 Fixed
Fixes https://github.com/Kotlin/kotlinx.serialization/issues/1574
This commit is contained in:
Leonid Startsev
2022-10-07 19:15:00 +02:00
committed by Space Team
parent 3c43416042
commit f1b1837f40
10 changed files with 286 additions and 564 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ir.BuiltinSymbolsBase
import org.jetbrains.kotlin.ir.deepCopyWithVariables
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
@@ -15,6 +16,7 @@ import org.jetbrains.kotlin.backend.jvm.ir.javaClassReference
import org.jetbrains.kotlin.backend.jvm.unboxInlineClass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -22,8 +24,11 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -41,6 +46,15 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
private val inInlineFunctionScope: Boolean
get() = allScopes.any { it.irElement.safeAs<IrDeclaration>()?.isInPublicInlineScope == true }
private val implementor = AnnotationPropertyImplementor(
jvmContext.irFactory,
jvmContext.irBuiltIns,
jvmContext.ir.symbols,
jvmContext.ir.symbols.javaLangClass,
jvmContext.ir.symbols.kClassJavaPropertyGetter.symbol,
ANNOTATION_IMPLEMENTATION
)
@Suppress("UNUSED_PARAMETER")
override fun chooseConstructor(implClass: IrClass, expression: IrConstructorCall) =
implClass.constructors.single()
@@ -53,88 +67,6 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
return super.visitConstructorCall(expression)
}
private fun IrType.kClassToJClassIfNeeded(): IrType = when {
this.isKClass() -> jvmContext.ir.symbols.javaLangClass.starProjectedType
this.isKClassArray() -> jvmContext.irBuiltIns.arrayClass.typeWith(
jvmContext.ir.symbols.javaLangClass.starProjectedType
)
else -> this
}
private fun IrType.isKClassArray() =
this is IrSimpleType && isArray() && arguments.single().typeOrNull?.isKClass() == true
private fun IrBuilderWithScope.kClassToJClass(irExpression: IrExpression): IrExpression =
irGet(
jvmContext.ir.symbols.javaLangClass.starProjectedType,
null,
jvmContext.ir.symbols.kClassJavaPropertyGetter.symbol
).apply {
extensionReceiver = irExpression
}
/**
* Copies array by one element, roughly as following:
* val size = kClassArray.size
* val result = arrayOfNulls<java.lang.Class>(size)
* var i = 0
* while(i < size) {
* result[i] = kClassArray[i].java
* i++
* }
* Partially taken from ArrayConstructorLowering.kt
*/
private fun IrBuilderWithScope.kClassArrayToJClassArray(kClassArray: IrExpression): IrExpression {
val javaLangClass = jvmContext.ir.symbols.javaLangClass.starProjectedType
val jlcArray = jvmContext.ir.symbols.array.typeWith(javaLangClass)
val arrayClass = jvmContext.ir.symbols.array.owner
val arrayOfNulls = jvmContext.ir.symbols.arrayOfNulls
val arraySizeSymbol = arrayClass.findDeclaration<IrProperty> { it.name.asString() == "size" }!!.getter!!
val block = irBlock {
val sourceArray = createTmpVariable(kClassArray, "src", isMutable = false)
val index = createTmpVariable(irInt(0), "i", isMutable = true)
val size = createTmpVariable(
irCall(arraySizeSymbol).apply { dispatchReceiver = irGet(sourceArray) },
"size", isMutable = false
)
val result = createTmpVariable(irCall(arrayOfNulls, jlcArray).apply {
listOf(javaLangClass)
putValueArgument(0, irGet(size))
})
val comparison = primitiveOp2(
startOffset, endOffset,
context.irBuiltIns.lessFunByOperandType[context.irBuiltIns.intType.classifierOrFail]!!,
context.irBuiltIns.booleanType,
IrStatementOrigin.LT,
irGet(index), irGet(size)
)
val setArraySymbol = arrayClass.functions.single { it.name == OperatorNameConventions.SET }
val getArraySymbol = arrayClass.functions.single { it.name == OperatorNameConventions.GET }
val inc = context.irBuiltIns.intType.getClass()!!.functions.single { it.name == OperatorNameConventions.INC }
+irWhile().also { loop ->
loop.condition = comparison
loop.body = irBlock {
val tempIndex = createTmpVariable(irGet(index))
val getArray = irCall(getArraySymbol).apply {
dispatchReceiver = irGet(sourceArray)
putValueArgument(0, irGet(tempIndex))
}
+irCall(setArraySymbol).apply {
dispatchReceiver = irGet(result)
putValueArgument(0, irGet(tempIndex))
putValueArgument(1, kClassToJClass(getArray))
}
+irSet(index.symbol, irCallOp(inc.symbol, index.type, irGet(index)))
}
}
+irGet(result)
}
return block
}
// There's no specialized Array.equals for unsigned arrays (as this is a Java function), so we force compiler not to box
// result of property getter call
override fun IrExpression.transformArrayEqualsArgument(type: IrType, irBuilder: IrBlockBodyBuilder): IrExpression =
@@ -184,84 +116,12 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
annotationClass: IrClass,
generatedConstructor: IrConstructor
) {
val ctorBodyBuilder = context.createIrBuilder(generatedConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
val ctorBody = context.irFactory.createBlockBody(
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, listOf(
IrDelegatingConstructorCallImpl(
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, context.irBuiltIns.unitType, context.irBuiltIns.anyClass.constructors.single(),
typeArgumentsCount = 0, valueArgumentsCount = 0
)
)
implementor.implementAnnotationPropertiesAndConstructor(
annotationClass.getAnnotationProperties(),
implClass,
generatedConstructor,
this
)
generatedConstructor.body = ctorBody
annotationClass.getAnnotationProperties().forEach { property ->
val propType = property.getter!!.returnType
val storedFieldType = propType.kClassToJClassIfNeeded()
val propName = property.name
val field = context.irFactory.buildField {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName
type = storedFieldType
origin = ANNOTATION_IMPLEMENTATION
isFinal = true
visibility = DescriptorVisibilities.PRIVATE
}.also { it.parent = implClass }
val parameter = generatedConstructor.addValueParameter(propName.asString(), propType)
val defaultExpression = property.backingField?.initializer?.expression
val newDefaultValue: IrExpressionBody? =
if (defaultExpression is IrGetValue && defaultExpression.symbol.owner is IrValueParameter) {
// INITIALIZE_PROPERTY_FROM_PARAMETER
(defaultExpression.symbol.owner as IrValueParameter).defaultValue
} else if (defaultExpression != null) {
property.backingField!!.initializer
} else null
parameter.defaultValue = newDefaultValue?.deepCopyWithVariables()?.also { it.transformChildrenVoid() }
ctorBody.statements += with(ctorBodyBuilder) {
val param = irGet(parameter)
val fieldValue = when {
propType.isKClass() -> kClassToJClass(param)
propType.isKClassArray() -> kClassArrayToJClassArray(param)
else -> param
}
irSetField(irGet(implClass.thisReceiver!!), field, fieldValue)
}
val prop = implClass.addProperty {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName
isVar = false
origin = ANNOTATION_IMPLEMENTATION
}.apply {
field.correspondingPropertySymbol = this.symbol
backingField = field
parent = implClass
overriddenSymbols = listOf(property.symbol)
}
prop.addGetter {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName // Annotation value getter should be named 'x', not 'getX'
returnType = propType.kClassToJClassIfNeeded() // On JVM, annotation store j.l.Class even if declared with KClass
origin = ANNOTATION_IMPLEMENTATION
visibility = DescriptorVisibilities.PUBLIC
modality = Modality.FINAL
}.apply {
correspondingPropertySymbol = prop.symbol
dispatchReceiverParameter = implClass.thisReceiver!!.copyTo(this)
body = context.createIrBuilder(symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+irReturn(irGetField(irGet(dispatchReceiverParameter!!), field))
}
overriddenSymbols = listOf(property.getter!!.symbol)
}
}
}
override fun generateFunctionBodies(
@@ -279,4 +139,183 @@ class JvmAnnotationImplementationTransformer(val jvmContext: JvmBackendContext,
generator.generateToStringMethod(toStringFun, implProperties)
}
class AnnotationPropertyImplementor(
val irFactory: IrFactory,
val irBuiltIns: IrBuiltIns,
val symbols: BuiltinSymbolsBase,
val javaLangClassSymbol: IrClassSymbol,
val kClassJavaPropertyGetterSymbol: IrSimpleFunctionSymbol,
val originForProp: IrDeclarationOrigin
) {
/**
* Copies array by one element, roughly as following:
* val size = kClassArray.size
* val result = arrayOfNulls<java.lang.Class>(size)
* var i = 0
* while(i < size) {
* result[i] = kClassArray[i].java
* i++
* }
* Partially taken from ArrayConstructorLowering.kt
*/
private fun IrBuilderWithScope.kClassArrayToJClassArray(kClassArray: IrExpression): IrExpression {
val javaLangClassType = javaLangClassSymbol.starProjectedType
val jlcArray = symbols.array.typeWith(javaLangClassType)
val arrayClass = symbols.array.owner
val arrayOfNulls = symbols.arrayOfNulls
val arraySizeSymbol = arrayClass.findDeclaration<IrProperty> { it.name.asString() == "size" }!!.getter!!
val block = irBlock {
val sourceArray = createTmpVariable(kClassArray, "src", isMutable = false)
val index = createTmpVariable(irInt(0), "i", isMutable = true)
val size = createTmpVariable(
irCall(arraySizeSymbol).apply { dispatchReceiver = irGet(sourceArray) },
"size", isMutable = false
)
val result = createTmpVariable(irCall(arrayOfNulls, jlcArray).apply {
listOf(javaLangClassType)
putValueArgument(0, irGet(size))
})
val comparison = primitiveOp2(
startOffset, endOffset,
context.irBuiltIns.lessFunByOperandType[context.irBuiltIns.intType.classifierOrFail]!!,
context.irBuiltIns.booleanType,
IrStatementOrigin.LT,
irGet(index), irGet(size)
)
val setArraySymbol = arrayClass.functions.single { it.name == OperatorNameConventions.SET }
val getArraySymbol = arrayClass.functions.single { it.name == OperatorNameConventions.GET }
val inc = context.irBuiltIns.intType.getClass()!!.functions.single { it.name == OperatorNameConventions.INC }
+irWhile().also { loop ->
loop.condition = comparison
loop.body = irBlock {
val tempIndex = createTmpVariable(irGet(index))
val getArray = irCall(getArraySymbol).apply {
dispatchReceiver = irGet(sourceArray)
putValueArgument(0, irGet(tempIndex))
}
+irCall(setArraySymbol).apply {
dispatchReceiver = irGet(result)
putValueArgument(0, irGet(tempIndex))
putValueArgument(1, kClassToJClass(getArray))
}
+irSet(index.symbol, irCallOp(inc.symbol, index.type, irGet(index)))
}
}
+irGet(result)
}
return block
}
private fun IrType.kClassToJClassIfNeeded(): IrType = when {
this.isKClass() -> javaLangClassSymbol.starProjectedType
this.isKClassArray() -> symbols.array.typeWith(
javaLangClassSymbol.starProjectedType
)
else -> this
}
private fun IrType.isKClassArray() =
this is IrSimpleType && isArray() && arguments.single().typeOrNull?.isKClass() == true
private fun IrBuilderWithScope.kClassToJClass(irExpression: IrExpression): IrExpression =
irGet(
javaLangClassSymbol.starProjectedType,
null,
kClassJavaPropertyGetterSymbol
).apply {
extensionReceiver = irExpression
}
fun implementAnnotationPropertiesAndConstructor(
annotationProperties: List<IrProperty>,
implClass: IrClass,
generatedConstructor: IrConstructor,
defaultValueTransformer: IrElementTransformerVoid?
) {
val ctorBodyBuilder = irBuiltIns.createIrBuilder(generatedConstructor.symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET)
val ctorBody = irFactory.createBlockBody(
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, listOf(
IrDelegatingConstructorCallImpl(
SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, irBuiltIns.unitType, irBuiltIns.anyClass.constructors.single(),
typeArgumentsCount = 0, valueArgumentsCount = 0
)
)
)
generatedConstructor.body = ctorBody
annotationProperties.forEach { property ->
val propType = property.getter!!.returnType
val storedFieldType = propType.kClassToJClassIfNeeded()
val propName = property.name
val field = irFactory.buildField {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName
type = storedFieldType
origin = originForProp
isFinal = true
visibility = DescriptorVisibilities.PRIVATE
}.also { it.parent = implClass }
val parameter = generatedConstructor.addValueParameter(propName.asString(), propType)
val defaultExpression = property.backingField?.initializer?.expression
val newDefaultValue: IrExpressionBody? =
if (defaultExpression is IrGetValue && defaultExpression.symbol.owner is IrValueParameter) {
// INITIALIZE_PROPERTY_FROM_PARAMETER
(defaultExpression.symbol.owner as IrValueParameter).defaultValue
} else if (defaultExpression != null) {
property.backingField!!.initializer
} else null
parameter.defaultValue = newDefaultValue?.deepCopyWithVariables()
?.also { if (defaultValueTransformer != null) it.transformChildrenVoid(defaultValueTransformer) }
ctorBody.statements += with(ctorBodyBuilder) {
val param = irGet(parameter)
val fieldValue = when {
propType.isKClass() -> kClassToJClass(param)
propType.isKClassArray() -> kClassArrayToJClassArray(param)
else -> param
}
irSetField(irGet(implClass.thisReceiver!!), field, fieldValue)
}
val prop = implClass.addProperty {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName
isVar = false
origin = originForProp
}.apply {
field.correspondingPropertySymbol = this.symbol
backingField = field
parent = implClass
overriddenSymbols = listOf(property.symbol)
}
prop.addGetter {
startOffset = SYNTHETIC_OFFSET
endOffset = SYNTHETIC_OFFSET
name = propName // Annotation value getter should be named 'x', not 'getX'
returnType = propType.kClassToJClassIfNeeded() // On JVM, annotation store j.l.Class even if declared with KClass
origin = originForProp
visibility = DescriptorVisibilities.PUBLIC
modality = Modality.FINAL
}.apply {
correspondingPropertySymbol = prop.symbol
dispatchReceiverParameter = implClass.thisReceiver!!.copyTo(this)
body = irBuiltIns.createIrBuilder(symbol, SYNTHETIC_OFFSET, SYNTHETIC_OFFSET).irBlockBody {
+irReturn(irGetField(irGet(dispatchReceiverParameter!!), field))
}
overriddenSymbols = listOf(property.getter!!.symbol)
}
}
}
}
}
@@ -10,6 +10,7 @@ dependencies {
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:backend.jvm"))
compileOnly(project(":compiler:backend.jvm.codegen"))
compileOnly(project(":compiler:backend.jvm.lower"))
compileOnly(project(":compiler:ir.tree"))
compileOnly(project(":js:js.frontend"))
compileOnly(project(":js:js.translator"))
@@ -366,7 +366,6 @@ interface IrBuilderWithPluginContext {
annotations.mapNotNull { annotationCall ->
val annotationClass = annotationCall.symbol.owner.parentAsClass
if (!annotationClass.isSerialInfoAnnotation) return@mapNotNull null
annotationCall.deepCopyWithVariables()
}
@@ -5,138 +5,29 @@
package org.jetbrains.kotlinx.serialization.compiler.backend.ir
import org.jetbrains.kotlin.backend.common.extensions.FirIncompatiblePluginAPI
import org.jetbrains.kotlin.backend.common.ir.addExtensionReceiver
import org.jetbrains.kotlin.backend.jvm.lower.JvmAnnotationImplementationTransformer
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
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.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.impl.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationPluginContext
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.
@OptIn(ObsoleteDescriptorBasedAPI::class)
class SerialInfoImplJvmIrGenerator(
private val context: SerializationPluginContext,
private val moduleFragment: IrModuleFragment,
) : IrBuilderWithPluginContext {
override val compilerContext: SerializationPluginContext
get() = context
private val jvmNameClass get() = context.referenceClass(ClassId.topLevel(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>()
fun getImplClass(serialInfoAnnotationClass: IrClass): IrClass =
annotationToImpl.getOrPut(serialInfoAnnotationClass) {
@OptIn(FirIncompatiblePluginAPI::class)
val implClassSymbol = context.referenceClass(serialInfoAnnotationClass.kotlinFqName.child(SerialEntityNames.IMPL_NAME))
implClassSymbol!!.owner.apply(this::generate)
}
fun generate(irClass: IrClass) {
if (!implGenerated.add(irClass)) return
val properties = irClass.declarations.filterIsInstance<IrProperty>()
if (properties.isEmpty()) return
val startOffset = UNDEFINED_OFFSET
val endOffset = UNDEFINED_OFFSET
val ctor = irClass.addConstructor {
visibility = DescriptorVisibilities.PUBLIC
}
val ctorBody = context.irFactory.createBlockBody(
startOffset, endOffset, listOf(
IrDelegatingConstructorCallImpl(
startOffset, endOffset, context.irBuiltIns.unitType, context.irBuiltIns.anyClass.constructors.single(),
typeArgumentsCount = 0, valueArgumentsCount = 0
)
)
)
ctor.body = ctorBody
for (property in properties) {
generateSimplePropertyWithBackingField(property.descriptor, irClass, Name.identifier("_" + property.name.asString()))
val getter = property.getter!!
getter.origin = SERIALIZATION_PLUGIN_ORIGIN
// Add JvmName annotation to property getters to force the resulting JVM method name for 'x' be 'x', instead of 'getX',
// and to avoid having useless bridges for it generated in BridgeLowering.
// Unfortunately, this results in an extra `@JvmName` annotation in the bytecode, but it shouldn't matter very much.
getter.annotations += jvmName(property.name.asString())
val field = property.backingField!!
field.visibility = DescriptorVisibilities.PRIVATE
field.origin = SERIALIZATION_PLUGIN_ORIGIN
val parameter = ctor.addValueParameter(property.name.asString(), field.type)
ctorBody.statements += IrSetFieldImpl(
startOffset, endOffset, field.symbol,
IrGetValueImpl(startOffset, endOffset, irClass.thisReceiver!!.symbol),
IrGetValueImpl(startOffset, endOffset, parameter.symbol),
context.irBuiltIns.unitType,
)
}
}
private fun jvmName(name: String): IrConstructorCall =
IrConstructorCallImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, jvmNameClass.defaultType, jvmNameClass.constructors.single().symbol,
typeArgumentsCount = 0, constructorTypeArgumentsCount = 0, valueArgumentsCount = 1,
).apply {
putValueArgument(0, IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.stringType, name))
}
@FirIncompatiblePluginAPI
fun KotlinType.toIrType() = compilerContext.typeTranslator.translateType(this)
private fun IrType.kClassToJClassIfNeeded(): IrType = when {
this.isKClass() -> javaLangType
this.isKClassArray() -> compilerContext.irBuiltIns.arrayClass.typeWith(javaLangType)
else -> this
}
private 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 {
@@ -168,8 +59,40 @@ class SerialInfoImplJvmIrGenerator(
}
}.symbol
private fun IrType.isKClassArray() =
this is IrSimpleType && isArray() && arguments.single().typeOrNull?.isKClass() == true
private val implementor = JvmAnnotationImplementationTransformer.AnnotationPropertyImplementor(
context.irFactory,
context.irBuiltIns,
context.symbols,
javaLangClass,
kClassJava.owner.getter!!.symbol,
SERIALIZATION_PLUGIN_ORIGIN
)
fun generateImplementationFor(annotationClass: IrClass) {
val properties = annotationClass.declarations.filterIsInstance<IrProperty>()
val subclass = context.irFactory.buildClass {
startOffset = UNDEFINED_OFFSET
endOffset = UNDEFINED_OFFSET
name = SerialEntityNames.IMPL_NAME
origin = SERIALIZATION_PLUGIN_ORIGIN
visibility = DescriptorVisibilities.PUBLIC
}.apply {
parent = annotationClass
createImplicitParameterDeclarationWithWrappedDescriptor()
superTypes = listOf(annotationClass.defaultType)
}
annotationClass.declarations.add(subclass)
val ctor = subclass.addConstructor {
startOffset = UNDEFINED_OFFSET
endOffset = UNDEFINED_OFFSET
visibility = DescriptorVisibilities.PUBLIC
}
implementor.implementAnnotationPropertiesAndConstructor(properties, subclass, ctor, null)
}
private fun createPackage(packageName: String): IrPackageFragment =
IrExternalPackageFragmentImpl.createEmptyExternalPackageFragment(
@@ -191,227 +114,4 @@ class SerialInfoImplJvmIrGenerator(
createImplicitParameterDeclarationWithWrappedDescriptor()
block(this)
}.symbol
private inline fun <reified T : IrDeclaration> IrClass.searchForDeclaration(descriptor: DeclarationDescriptor): T? {
return declarations.singleOrNull { it.descriptor == descriptor } as? T
}
private fun generateSimplePropertyWithBackingField(
propertyDescriptor: PropertyDescriptor,
propertyParent: IrClass,
fieldName: Name = propertyDescriptor.name,
): IrProperty {
val irProperty = propertyParent.searchForDeclaration(propertyDescriptor) ?: run {
with(propertyDescriptor) {
propertyParent.factory.createProperty(
propertyParent.startOffset,
propertyParent.endOffset,
SERIALIZATION_PLUGIN_ORIGIN,
IrPropertySymbolImpl(propertyDescriptor),
name,
visibility,
modality,
isVar,
isConst,
isLateInit,
isDelegated,
isExternal
).also {
it.parent = propertyParent
propertyParent.addMember(it)
}
}
}
propertyParent.generatePropertyBackingFieldIfNeeded(propertyDescriptor, irProperty, fieldName)
val fieldSymbol = irProperty.backingField!!.symbol
irProperty.getter = propertyDescriptor.getter?.let {
propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = true)
}?.apply { parent = propertyParent }
irProperty.setter = propertyDescriptor.setter?.let {
propertyParent.generatePropertyAccessor(propertyDescriptor, irProperty, it, fieldSymbol, isGetter = false)
}?.apply { parent = propertyParent }
return irProperty
}
private fun IrClass.generatePropertyBackingFieldIfNeeded(
propertyDescriptor: PropertyDescriptor,
originProperty: IrProperty,
name: Name,
) {
if (originProperty.backingField != null) return
val field = with(propertyDescriptor) {
@OptIn(FirIncompatiblePluginAPI::class)// should be called only with old FE
originProperty.factory.createField(
originProperty.startOffset,
originProperty.endOffset,
SERIALIZATION_PLUGIN_ORIGIN,
IrFieldSymbolImpl(propertyDescriptor),
name,
type.toIrType(),
visibility,
!isVar,
isEffectivelyExternal(),
dispatchReceiverParameter == null
)
}
field.apply {
parent = this@generatePropertyBackingFieldIfNeeded
correspondingPropertySymbol = originProperty.symbol
}
originProperty.backingField = field
}
private fun IrClass.generatePropertyAccessor(
propertyDescriptor: PropertyDescriptor,
property: IrProperty,
descriptor: PropertyAccessorDescriptor,
fieldSymbol: IrFieldSymbol,
isGetter: Boolean,
): IrSimpleFunction {
val irAccessor: IrSimpleFunction = when (isGetter) {
true -> searchForDeclaration<IrProperty>(propertyDescriptor)?.getter
false -> searchForDeclaration<IrProperty>(propertyDescriptor)?.setter
} ?: run {
with(descriptor) {
@OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
property.factory.createFunction(
fieldSymbol.owner.startOffset,
fieldSymbol.owner.endOffset,
SERIALIZATION_PLUGIN_ORIGIN, IrSimpleFunctionSymbolImpl(descriptor),
name, visibility, modality, returnType!!.toIrType(),
isInline, isEffectivelyExternal(), isTailrec, isSuspend, isOperator, isInfix, isExpect
)
}.also { f ->
generateOverriddenFunctionSymbols(f, compilerContext.symbolTable)
f.createParameterDeclarations(descriptor)
@OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
f.returnType = descriptor.returnType!!.toIrType()
f.correspondingPropertySymbol = fieldSymbol.owner.correspondingPropertySymbol
}
}
irAccessor.body = when (isGetter) {
true -> generateDefaultGetterBody(irAccessor)
false -> generateDefaultSetterBody(irAccessor)
}
return irAccessor
}
private fun generateDefaultGetterBody(
irAccessor: IrSimpleFunction
): IrBlockBody {
val irProperty =
irAccessor.correspondingPropertySymbol?.owner ?: error("Expected corresponding property for accessor ${irAccessor.render()}")
val startOffset = irAccessor.startOffset
val endOffset = irAccessor.endOffset
val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol)
val propertyIrType = irAccessor.returnType
irBody.statements.add(
IrReturnImpl(
startOffset, endOffset, compilerContext.irBuiltIns.nothingType,
irAccessor.symbol,
IrGetFieldImpl(
startOffset, endOffset,
irProperty.backingField?.symbol ?: error("Property expected to have backing field"),
propertyIrType,
receiver
).let {
if (propertyIrType.isKClass()) {
irAccessor.returnType = irAccessor.returnType.kClassToJClassIfNeeded()
kClassExprToJClassIfNeeded(startOffset, endOffset, it)
} else it
}
)
)
return irBody
}
private fun generateDefaultSetterBody(
irAccessor: IrSimpleFunction
): IrBlockBody {
val irProperty =
irAccessor.correspondingPropertySymbol?.owner ?: error("Expected corresponding property for accessor ${irAccessor.render()}")
val startOffset = irAccessor.startOffset
val endOffset = irAccessor.endOffset
val irBody = irAccessor.factory.createBlockBody(startOffset, endOffset)
val receiver = generateReceiverExpressionForFieldAccess(irAccessor.dispatchReceiverParameter!!.symbol)
val irValueParameter = irAccessor.valueParameters.single()
irBody.statements.add(
IrSetFieldImpl(
startOffset, endOffset,
irProperty.backingField?.symbol ?: error("Property ${irProperty.render()} expected to have backing field"),
receiver,
IrGetValueImpl(startOffset, endOffset, irValueParameter.type, irValueParameter.symbol),
compilerContext.irBuiltIns.unitType
)
)
return irBody
}
private fun generateReceiverExpressionForFieldAccess(
ownerSymbol: IrValueSymbol
): IrExpression = IrGetValueImpl(
ownerSymbol.owner.startOffset, ownerSymbol.owner.endOffset,
ownerSymbol
)
private fun IrFunction.createParameterDeclarations(
descriptor: FunctionDescriptor,
overwriteValueParameters: Boolean = false,
copyTypeParameters: Boolean = true
) {
val function = this
fun irValueParameter(descriptor: ParameterDescriptor): IrValueParameter = with(descriptor) {
@OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
factory.createValueParameter(
function.startOffset, function.endOffset, SERIALIZATION_PLUGIN_ORIGIN, IrValueParameterSymbolImpl(this),
name, indexOrMinusOne, type.toIrType(), varargElementType?.toIrType(), isCrossinline, isNoinline,
isHidden = false, isAssignable = false
).also {
it.parent = function
}
}
if (copyTypeParameters) {
assert(typeParameters.isEmpty())
copyTypeParamsFromDescriptor(descriptor)
}
dispatchReceiverParameter = descriptor.dispatchReceiverParameter?.let { irValueParameter(it) }
extensionReceiverParameter = descriptor.extensionReceiverParameter?.let { irValueParameter(it) }
if (!overwriteValueParameters)
assert(valueParameters.isEmpty())
valueParameters = descriptor.valueParameters.map { irValueParameter(it) }
}
private fun IrFunction.copyTypeParamsFromDescriptor(descriptor: FunctionDescriptor) {
val newTypeParameters = descriptor.typeParameters.map {
factory.createTypeParameter(
startOffset, endOffset,
SERIALIZATION_PLUGIN_ORIGIN,
IrTypeParameterSymbolImpl(it),
it.name, it.index, it.isReified, it.variance
).also { typeParameter ->
typeParameter.parent = this
}
}
@OptIn(FirIncompatiblePluginAPI::class) // should never be called after FIR frontend
newTypeParameters.forEach { typeParameter ->
typeParameter.superTypes = typeParameter.descriptor.upperBounds.map { it.toIrType() }
}
typeParameters = newTypeParameters
}
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.ir.fileParent
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
@@ -29,7 +28,6 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.*
import org.jetbrains.kotlinx.serialization.compiler.backend.ir.SerializationJvmIrIntrinsicSupport
import org.jetbrains.kotlinx.serialization.compiler.resolve.KSerializerDescriptorResolver
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerialEntityNames
import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackages
import java.util.concurrent.ConcurrentHashMap
@@ -53,7 +51,6 @@ fun ClassLoweringPass.runOnFileInOrder(irFile: IrFile) {
class SerializationPluginContext(baseContext: IrPluginContext, val metadataPlugin: SerializationDescriptorSerializerPlugin?) :
IrPluginContext by baseContext, SerializationBaseContext {
lateinit var serialInfoImplJvmIrGenerator: SerialInfoImplJvmIrGenerator
internal val copiedStaticWriteSelf: MutableMap<IrSimpleFunction, IrSimpleFunction> = ConcurrentHashMap()
@@ -95,8 +92,9 @@ private class SerializerClassLowering(
moduleFragment: IrModuleFragment
) : IrElementTransformerVoid(), ClassLoweringPass {
val context: SerializationPluginContext = SerializationPluginContext(baseContext, metadataPlugin)
private val serialInfoJvmGenerator =
SerialInfoImplJvmIrGenerator(context, moduleFragment).also { context.serialInfoImplJvmIrGenerator = it }
// Lazy to avoid creating generator in non-JVM backends
private val serialInfoJvmGenerator by lazy(LazyThreadSafetyMode.NONE) { SerialInfoImplJvmIrGenerator(context, moduleFragment) }
override fun lower(irClass: IrClass) {
irClass.runPluginSafe {
@@ -104,9 +102,8 @@ private class SerializerClassLowering(
SerializerIrGenerator.generate(irClass, context, context.metadataPlugin)
SerializableCompanionIrGenerator.generate(irClass, context)
@OptIn(ObsoleteDescriptorBasedAPI::class)
if (context.platform.isJvm() && KSerializerDescriptorResolver.isSerialInfoImpl(irClass.descriptor)) {
serialInfoJvmGenerator.generate(irClass)
if (context.platform.isJvm() && irClass.isSerialInfoAnnotation) {
serialInfoJvmGenerator.generateImplementationFor(irClass)
}
}
}
@@ -24,14 +24,13 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.*
open class SerializationResolveExtension @JvmOverloads constructor(val metadataPlugin: SerializationDescriptorSerializerPlugin? = null) : SyntheticResolveExtension {
override fun getSyntheticNestedClassNames(thisDescriptor: ClassDescriptor): List<Name> = when {
thisDescriptor.isSerialInfoAnnotation && thisDescriptor.platform?.isJvm() == true -> listOf(SerialEntityNames.IMPL_NAME)
(thisDescriptor.shouldHaveGeneratedSerializer) && !thisDescriptor.hasCompanionObjectAsSerializer ->
listOf(SerialEntityNames.SERIALIZER_CLASS_NAME)
else -> listOf()
}
override fun getPossibleSyntheticNestedClassNames(thisDescriptor: ClassDescriptor): List<Name>? {
return listOf(SerialEntityNames.IMPL_NAME, SerialEntityNames.SERIALIZER_CLASS_NAME)
return listOf(SerialEntityNames.SERIALIZER_CLASS_NAME)
}
override fun getSyntheticFunctionNames(thisDescriptor: ClassDescriptor): List<Name> = when {
@@ -73,9 +72,7 @@ open class SerializationResolveExtension @JvmOverloads constructor(val metadataP
declarationProvider: ClassMemberDeclarationProvider,
result: MutableSet<ClassDescriptor>
) {
if (thisDescriptor.isSerialInfoAnnotation && name == SerialEntityNames.IMPL_NAME)
result.add(KSerializerDescriptorResolver.addSerialInfoImplClass(thisDescriptor, declarationProvider, ctx))
else if (thisDescriptor.shouldHaveGeneratedSerializer && name == SerialEntityNames.SERIALIZER_CLASS_NAME &&
if (thisDescriptor.shouldHaveGeneratedSerializer && name == SerialEntityNames.SERIALIZER_CLASS_NAME &&
result.none { it.name == SerialEntityNames.SERIALIZER_CLASS_NAME }
)
result.add(KSerializerDescriptorResolver.addSerializerImplClass(thisDescriptor, declarationProvider, ctx))
@@ -88,7 +85,6 @@ open class SerializationResolveExtension @JvmOverloads constructor(val metadataP
else null
override fun addSyntheticSupertypes(thisDescriptor: ClassDescriptor, supertypes: MutableList<KotlinType>) {
KSerializerDescriptorResolver.addSerialInfoSuperType(thisDescriptor, supertypes)
KSerializerDescriptorResolver.addSerializerSupertypes(thisDescriptor, supertypes)
KSerializerDescriptorResolver.addSerializerFactorySuperType(thisDescriptor, supertypes)
}
@@ -125,7 +121,6 @@ open class SerializationResolveExtension @JvmOverloads constructor(val metadataP
fromSupertypes: ArrayList<PropertyDescriptor>,
result: MutableSet<PropertyDescriptor>
) {
KSerializerDescriptorResolver.generateDescriptorsForAnnotationImpl(thisDescriptor, fromSupertypes, result)
KSerializerDescriptorResolver.generateSerializerProperties(thisDescriptor, fromSupertypes, name, result)
}
}
@@ -47,12 +47,6 @@ object KSerializerDescriptorResolver {
&& (thisDescriptor.containingDeclaration as ClassDescriptor).isSerialInfoAnnotation
}
fun addSerialInfoSuperType(thisDescriptor: ClassDescriptor, supertypes: MutableList<KotlinType>) {
if (isSerialInfoImpl(thisDescriptor)) {
supertypes.add((thisDescriptor.containingDeclaration as LazyClassDescriptor).toSimpleType(false))
}
}
fun addSerializerFactorySuperType(classDescriptor: ClassDescriptor, supertypes: MutableList<KotlinType>) {
if (!classDescriptor.needSerializerFactory()) return
val serializerFactoryClass =
@@ -72,36 +66,6 @@ object KSerializerDescriptorResolver {
supertypes.add(classDescriptor.createSerializerTypeFor(serializableClassDescriptor.defaultType, fqName))
}
fun addSerialInfoImplClass(
interfaceDesc: ClassDescriptor,
declarationProvider: ClassMemberDeclarationProvider,
ctx: LazyClassContext
): ClassDescriptor {
val interfaceDecl = declarationProvider.correspondingClassOrObject!!
val scope = ctx.declarationScopeProvider.getResolutionScopeForDeclaration(declarationProvider.ownerInfo!!.scopeAnchor)
val props = interfaceDecl.primaryConstructorParameters
// if there is some properties, there will be a public synthetic constructor at the codegen phase
val primaryCtorVisibility = if (props.isEmpty()) DescriptorVisibilities.PUBLIC else DescriptorVisibilities.PRIVATE
val descriptor = SyntheticClassOrObjectDescriptor(
ctx,
interfaceDecl,
interfaceDesc,
IMPL_NAME,
interfaceDesc.source,
scope,
Modality.FINAL,
DescriptorVisibilities.PUBLIC,
Annotations.create(listOf(createDeprecatedHiddenAnnotation(interfaceDesc.module))),
primaryCtorVisibility,
ClassKind.CLASS,
false
)
descriptor.initialize()
return descriptor
}
fun addSerializerImplClass(
thisDescriptor: ClassDescriptor,
declarationProvider: ClassMemberDeclarationProvider,
@@ -628,23 +592,6 @@ object KSerializerDescriptorResolver {
return f
}
fun generateDescriptorsForAnnotationImpl(
thisDescriptor: ClassDescriptor,
fromSupertypes: List<PropertyDescriptor>,
result: MutableCollection<PropertyDescriptor>
) {
if (isSerialInfoImpl(thisDescriptor)) {
result.add(
fromSupertypes.first().newCopyBuilder().apply {
setOwner(thisDescriptor)
setModality(Modality.FINAL)
setKind(CallableMemberDescriptor.Kind.SYNTHESIZED)
setDispatchReceiverParameter(thisDescriptor.thisAsReceiverParameter)
}.build()!!
)
}
}
// create properties typeSerial0, typeSerial1, etc... for storing generic arguments' serializers
private fun createLocalSerializersFieldsDescriptor(
name: Name,
@@ -0,0 +1,32 @@
// WITH_STDLIB
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
@SerialInfo
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
annotation class MyId(val id: Int, val type: String = "foo")
@Serializable
@MyId(10)
class Foo(@MyId(20) val i: Int)
fun box(): String {
val desc = Foo.serializer().descriptor
val classId = desc.annotations.filterIsInstance<MyId>().single()
val propId = desc.getElementAnnotations(0).filterIsInstance<MyId>().single().id
if (classId.id != 10) return "Incorrect class annotation: ${classId}"
if (classId.type != "foo") return "Incorrect default argument: ${classId}"
if (!classId::class.java.toString().contains("annotationImpl")) return "Backend doesn't use annotation instantiation: ${classId::class}"
if (propId != 20) return "Incorrect propery annotation: $propId"
val implClassJava = Class.forName("MyId\$Impl")
if (implClassJava.toString() != "class MyId\$Impl") return "Old annotation implementations are not preserved for compatibility"
val ctorStr = implClassJava.constructors.toList().toString()
if (!ctorStr.contains("public MyId\$Impl(int,java.lang.String)")) return "Compatibility impl does not contain correct constructor: $ctorStr"
val methodsStr = implClassJava.methods.toList().toString()
if (!methodsStr.contains("public final int MyId\$Impl.id()")) return "Compatibility impl does not contain correct methods: $methodsStr"
if (!methodsStr.contains("public final java.lang.String MyId\$Impl.type()")) return "Compatibility impl does not contain correct methods: $methodsStr"
return "OK"
}
@@ -105,6 +105,12 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization
runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt");
}
@Test
@TestMetadata("serialInfo.kt")
public void testSerialInfo() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/serialInfo.kt");
}
@Test
@TestMetadata("serializableOnPropertyType.kt")
public void testSerializableOnPropertyType() throws Exception {
@@ -103,6 +103,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
runTest("plugins/kotlinx-serialization/testData/boxIr/sealedInterfaces.kt");
}
@Test
@TestMetadata("serialInfo.kt")
public void testSerialInfo() throws Exception {
runTest("plugins/kotlinx-serialization/testData/boxIr/serialInfo.kt");
}
@Test
@TestMetadata("serializableOnPropertyType.kt")
public void testSerializableOnPropertyType() throws Exception {