[IR, Serialization] Support kotlinx-based (de)serialization of MFVC, nullable MFVC value assignment to nonnull variable
Signed-off-by: Evgeniy.Zhelenskiy <Evgeniy.Zhelenskiy@jetbrains.com> #KT-1179
This commit is contained in:
+29
-8
@@ -105,10 +105,15 @@ internal class JvmMultiFieldValueClassLowering(
|
|||||||
return expressions.subList(0, expressions.size - repeatable.size) to repeatable
|
return expressions.subList(0, expressions.size - repeatable.size) to repeatable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrBuilderWithScope.castedToNotNull(expression: IrExpression) =
|
||||||
|
if (expression.type.isNullable()) irImplicitCast(expression, expression.type.makeNotNull()) else expression
|
||||||
|
|
||||||
fun IrBlockBuilder.addReplacement(expression: IrSetValue, safe: Boolean): IrExpression? {
|
fun IrBlockBuilder.addReplacement(expression: IrSetValue, safe: Boolean): IrExpression? {
|
||||||
oldValueSymbol2NewValueSymbol[expression.symbol]?.let { return irSet(it.owner, expression.value) }
|
oldValueSymbol2NewValueSymbol[expression.symbol]?.let {
|
||||||
|
return irSet(it.owner, expression.value).also { irSet -> +irSet }
|
||||||
|
}
|
||||||
val instance = oldSymbol2MfvcNodeInstance[expression.symbol] ?: return null
|
val instance = oldSymbol2MfvcNodeInstance[expression.symbol] ?: return null
|
||||||
val values: List<IrExpression> = makeFlattenedExpressionsWithGivenSafety(instance.node, safe, expression.value)
|
val values: List<IrExpression> = makeFlattenedExpressionsWithGivenSafety(instance.node, safe, castedToNotNull(expression.value))
|
||||||
val setterExpressions = instance.makeSetterExpressions(this, values)
|
val setterExpressions = instance.makeSetterExpressions(this, values)
|
||||||
expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values)
|
expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values)
|
||||||
+setterExpressions
|
+setterExpressions
|
||||||
@@ -151,12 +156,17 @@ internal class JvmMultiFieldValueClassLowering(
|
|||||||
|
|
||||||
fun IrBlockBuilder.addReplacement(expression: IrSetField, safe: Boolean): IrExpression? {
|
fun IrBlockBuilder.addReplacement(expression: IrSetField, safe: Boolean): IrExpression? {
|
||||||
val field = expression.field
|
val field = expression.field
|
||||||
expression.receiver?.get(this, field.name)?.let { +it; return it }
|
|
||||||
val node = replacements.getMfvcFieldNode(field) ?: return null
|
val node = replacements.getMfvcFieldNode(field) ?: return null
|
||||||
val typeArguments = makeTypeArgumentsFromField(expression)
|
val instance = expression.receiver?.get(this, field.name)?.let {
|
||||||
val instance: ReceiverBasedMfvcNodeInstance =
|
(expression2MfvcNodeInstanceAccessor[it] as MfvcNodeInstanceAccessor.Getter).instance
|
||||||
node.createInstanceFromBox(this, typeArguments, expression.receiver, AccessType.AlwaysPrivate, ::variablesSaver)
|
} ?: node.createInstanceFromBox(
|
||||||
val values: List<IrExpression> = makeFlattenedExpressionsWithGivenSafety(node, safe, expression.value)
|
scope = this,
|
||||||
|
typeArguments = makeTypeArgumentsFromField(expression),
|
||||||
|
receiver = expression.receiver,
|
||||||
|
accessType = AccessType.AlwaysPrivate,
|
||||||
|
saveVariable = ::variablesSaver
|
||||||
|
)
|
||||||
|
val values: List<IrExpression> = makeFlattenedExpressionsWithGivenSafety(node, safe, castedToNotNull(expression.value))
|
||||||
val setterExpressions = instance.makeSetterExpressions(this, values)
|
val setterExpressions = instance.makeSetterExpressions(this, values)
|
||||||
expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values)
|
expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values)
|
||||||
+setterExpressions
|
+setterExpressions
|
||||||
@@ -415,6 +425,12 @@ internal class JvmMultiFieldValueClassLowering(
|
|||||||
override fun visitClass(declaration: IrClass): IrStatement = declaration
|
override fun visitClass(declaration: IrClass): IrStatement = declaration
|
||||||
|
|
||||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
||||||
|
if (expression.symbol.owner.constructedClass != constructor.constructedClass) { // Delegating constructor to Object
|
||||||
|
require(expression.symbol.owner.constructedClass == context.irBuiltIns.anyClass.owner) {
|
||||||
|
"Expected delegating constructor to the MFVC primary constructor or Any constructor but got: ${expression.symbol.owner.render()}"
|
||||||
|
}
|
||||||
|
return irBlock { }
|
||||||
|
}
|
||||||
val oldPrimaryConstructor = replacements.getRootMfvcNode(constructor.constructedClass).oldPrimaryConstructor
|
val oldPrimaryConstructor = replacements.getRootMfvcNode(constructor.constructedClass).oldPrimaryConstructor
|
||||||
thisVar.initializer = irCall(oldPrimaryConstructor).apply {
|
thisVar.initializer = irCall(oldPrimaryConstructor).apply {
|
||||||
copyTypeAndValueArgumentsFrom(expression)
|
copyTypeAndValueArgumentsFrom(expression)
|
||||||
@@ -1252,7 +1268,12 @@ internal class JvmMultiFieldValueClassLowering(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val transformedExpression = expression.transform(this@JvmMultiFieldValueClassLowering, null)
|
val nullableTransformedExpression = expression.transform(this@JvmMultiFieldValueClassLowering, null)
|
||||||
|
val transformedExpression =
|
||||||
|
if (nullableTransformedExpression.type.isNullable())
|
||||||
|
irImplicitCast(nullableTransformedExpression, nullableTransformedExpression.type.makeNotNull())
|
||||||
|
else
|
||||||
|
nullableTransformedExpression
|
||||||
val addedSettersToFlattened = valueDeclarationsRemapper.handleFlattenedGetterExpressions(this, transformedExpression) {
|
val addedSettersToFlattened = valueDeclarationsRemapper.handleFlattenedGetterExpressions(this, transformedExpression) {
|
||||||
require(it.size == instance.size) { "Incompatible assignment sizes: ${it.size}, ${instance.size}" }
|
require(it.size == instance.size) { "Incompatible assignment sizes: ${it.size}, ${instance.size}" }
|
||||||
instance.makeSetterExpressions(this, it)
|
instance.makeSetterExpressions(this, it)
|
||||||
|
|||||||
+2
-1
@@ -231,7 +231,8 @@ class MemoizedMultiFieldValueClassReplacements(
|
|||||||
function.isStaticValueClassReplacement ||
|
function.isStaticValueClassReplacement ||
|
||||||
function.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER && function.isAccessor ||
|
function.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER && function.isAccessor ||
|
||||||
function.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD ||
|
function.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD ||
|
||||||
function.origin.isSynthetic && function.origin != IrDeclarationOrigin.SYNTHETIC_GENERATED_SAM_IMPLEMENTATION ||
|
(function.origin.isSynthetic && function.origin != IrDeclarationOrigin.SYNTHETIC_GENERATED_SAM_IMPLEMENTATION &&
|
||||||
|
!(function is IrConstructor && function.constructedClass.isMultiFieldValueClass && !function.isPrimary)) ||
|
||||||
function.isMultiFieldValueClassFieldGetter -> null
|
function.isMultiFieldValueClassFieldGetter -> null
|
||||||
|
|
||||||
(function.parent as? IrClass)?.isMultiFieldValueClass == true -> when {
|
(function.parent as? IrClass)?.isMultiFieldValueClass == true -> when {
|
||||||
|
|||||||
+3
-2
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
|||||||
import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter
|
import org.jetbrains.kotlin.ir.builders.declarations.addTypeParameter
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.isSingleFieldValueClass
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.util.companionObject
|
import org.jetbrains.kotlin.ir.util.companionObject
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
@@ -102,7 +103,7 @@ class IrPreGenerator(
|
|||||||
if (irClass.hasCompanionObjectAsSerializer && irClass.companionObject()
|
if (irClass.hasCompanionObjectAsSerializer && irClass.companionObject()
|
||||||
?.findPluginGeneratedMethod(SerialEntityNames.LOAD, compilerContext.afterK2) == null
|
?.findPluginGeneratedMethod(SerialEntityNames.LOAD, compilerContext.afterK2) == null
|
||||||
) return
|
) return
|
||||||
if (irClass.isValue) return
|
if (irClass.isSingleFieldValueClass) return
|
||||||
if (irClass.findSerializableSyntheticConstructor() != null) return
|
if (irClass.findSerializableSyntheticConstructor() != null) return
|
||||||
val ctor = irClass.addConstructor {
|
val ctor = irClass.addConstructor {
|
||||||
origin = SERIALIZATION_PLUGIN_ORIGIN
|
origin = SERIALIZATION_PLUGIN_ORIGIN
|
||||||
@@ -121,7 +122,7 @@ class IrPreGenerator(
|
|||||||
ctor.addValueParameter(prop.name, prop.type.makeNullableIfNotPrimitive(), SERIALIZATION_PLUGIN_ORIGIN)
|
ctor.addValueParameter(prop.name, prop.type.makeNullableIfNotPrimitive(), SERIALIZATION_PLUGIN_ORIGIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctor.addValueParameter(SerialEntityNames.dummyParamName, markerClassSymbol.defaultType, SERIALIZATION_PLUGIN_ORIGIN)
|
ctor.addValueParameter(SerialEntityNames.dummyParamName, markerClassSymbol.defaultType.makeNullable(), SERIALIZATION_PLUGIN_ORIGIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrType.makeNullableIfNotPrimitive() =
|
private fun IrType.makeNullableIfNotPrimitive() =
|
||||||
|
|||||||
+1
-1
@@ -253,4 +253,4 @@ fun IrSimpleType.argumentTypesOrUpperBounds(): List<IrType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) =
|
internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) =
|
||||||
!isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null)
|
!isSingleFieldValueClass && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null)
|
||||||
|
|||||||
+1
-1
@@ -634,7 +634,7 @@ open class SerializerIrGenerator(
|
|||||||
irClass,
|
irClass,
|
||||||
context
|
context
|
||||||
)
|
)
|
||||||
serializableDesc.isValue -> SerializerForInlineClassGenerator(irClass, context)
|
serializableDesc.isSingleFieldValueClass -> SerializerForInlineClassGenerator(irClass, context)
|
||||||
else -> SerializerIrGenerator(irClass, context, metadataPlugin)
|
else -> SerializerIrGenerator(irClass, context, metadataPlugin)
|
||||||
}
|
}
|
||||||
generator.generate()
|
generator.generate()
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.diagnostics.*
|
|||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.isValueClass
|
import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
|
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||||
@@ -485,7 +485,7 @@ object FirSerializationPluginClassChecker : FirClassChecker() {
|
|||||||
context(CheckerContext)
|
context(CheckerContext)
|
||||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||||
private val ConeKotlinType.isUnsupportedInlineType: Boolean
|
private val ConeKotlinType.isUnsupportedInlineType: Boolean
|
||||||
get() = isValueClass(session) && !isPrimitiveOrNullablePrimitive
|
get() = isSingleFieldValueClass(session) && !isPrimitiveOrNullablePrimitive
|
||||||
|
|
||||||
context(CheckerContext)
|
context(CheckerContext)
|
||||||
@Suppress("IncorrectFormatting") // KTIJ-22227
|
@Suppress("IncorrectFormatting") // KTIJ-22227
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// TARGET_BACKEND: JVM_IR
|
||||||
|
// WITH_STDLIB
|
||||||
|
// LANGUAGE: +ValueClasses
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import kotlinx.serialization.internal.*
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed interface I
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class DPoint(val x: Double, val y: Double): I
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class DSegment(val p1: DPoint, val p2: DPoint): I
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PointWrapper(val value: DPoint)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SegmentWrapper(val value: DSegment)
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val p1 = DPoint(1.0, 2.0)
|
||||||
|
val dSegment = DSegment(p1, DPoint(3.0, 4.0))
|
||||||
|
run {
|
||||||
|
val s = Json.encodeToString(DPoint.serializer(), p1)
|
||||||
|
if (s != """{"x":1.0,"y":2.0}""") return s
|
||||||
|
val decoded = Json.decodeFromString(DPoint.serializer(), s)
|
||||||
|
if (p1 != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val s = Json.encodeToString(DSegment.serializer(), dSegment)
|
||||||
|
if (s != """{"p1":{"x":1.0,"y":2.0},"p2":{"x":3.0,"y":4.0}}""") return s
|
||||||
|
val decoded = Json.decodeFromString(DSegment.serializer(), s)
|
||||||
|
if (dSegment != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val pointWrapper = PointWrapper(p1)
|
||||||
|
val s = Json.encodeToString(PointWrapper.serializer(), pointWrapper)
|
||||||
|
if (s != """{"value":{"x":1.0,"y":2.0}}""") return s
|
||||||
|
val decoded = Json.decodeFromString(PointWrapper.serializer(), s)
|
||||||
|
if (pointWrapper != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val segmentWrapper = SegmentWrapper(dSegment)
|
||||||
|
val s = Json.encodeToString(SegmentWrapper.serializer(), segmentWrapper)
|
||||||
|
if (s != """{"value":{"p1":{"x":1.0,"y":2.0},"p2":{"x":3.0,"y":4.0}}}""") return s
|
||||||
|
val decoded = Json.decodeFromString(SegmentWrapper.serializer(), s)
|
||||||
|
if (segmentWrapper != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val s = Json.encodeToString(I.serializer(), p1)
|
||||||
|
if (s != """{"type":"DPoint","x":1.0,"y":2.0}""") return s
|
||||||
|
val decoded = Json.decodeFromString(I.serializer(), s)
|
||||||
|
if (p1 != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
run {
|
||||||
|
val s = Json.encodeToString(I.serializer(), dSegment)
|
||||||
|
if (s != """{"type":"DSegment","p1":{"x":1.0,"y":2.0},"p2":{"x":3.0,"y":4.0}}""") return s
|
||||||
|
val decoded = Json.decodeFromString(I.serializer(), s)
|
||||||
|
if (dSegment != decoded) return decoded.toString()
|
||||||
|
}
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+6
@@ -93,6 +93,12 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/metaSerializable.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/metaSerializable.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("multiFieldValueClasses.kt")
|
||||||
|
public void testMultiFieldValueClasses() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/multiFieldValueClasses.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("multimoduleInheritance.kt")
|
@TestMetadata("multimoduleInheritance.kt")
|
||||||
public void testMultimoduleInheritance() throws Exception {
|
public void testMultimoduleInheritance() throws Exception {
|
||||||
|
|||||||
+6
@@ -91,6 +91,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT
|
|||||||
runTest("plugins/kotlinx-serialization/testData/boxIr/metaSerializable.kt");
|
runTest("plugins/kotlinx-serialization/testData/boxIr/metaSerializable.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("multiFieldValueClasses.kt")
|
||||||
|
public void testMultiFieldValueClasses() throws Exception {
|
||||||
|
runTest("plugins/kotlinx-serialization/testData/boxIr/multiFieldValueClasses.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("multimoduleInheritance.kt")
|
@TestMetadata("multimoduleInheritance.kt")
|
||||||
public void testMultimoduleInheritance() throws Exception {
|
public void testMultimoduleInheritance() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user