diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt index 797195847cd..c566cc5abc9 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt @@ -105,10 +105,15 @@ internal class JvmMultiFieldValueClassLowering( 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? { - 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 values: List = makeFlattenedExpressionsWithGivenSafety(instance.node, safe, expression.value) + val values: List = makeFlattenedExpressionsWithGivenSafety(instance.node, safe, castedToNotNull(expression.value)) val setterExpressions = instance.makeSetterExpressions(this, values) expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values) +setterExpressions @@ -151,12 +156,17 @@ internal class JvmMultiFieldValueClassLowering( fun IrBlockBuilder.addReplacement(expression: IrSetField, safe: Boolean): IrExpression? { val field = expression.field - expression.receiver?.get(this, field.name)?.let { +it; return it } val node = replacements.getMfvcFieldNode(field) ?: return null - val typeArguments = makeTypeArgumentsFromField(expression) - val instance: ReceiverBasedMfvcNodeInstance = - node.createInstanceFromBox(this, typeArguments, expression.receiver, AccessType.AlwaysPrivate, ::variablesSaver) - val values: List = makeFlattenedExpressionsWithGivenSafety(node, safe, expression.value) + val instance = expression.receiver?.get(this, field.name)?.let { + (expression2MfvcNodeInstanceAccessor[it] as MfvcNodeInstanceAccessor.Getter).instance + } ?: node.createInstanceFromBox( + scope = this, + typeArguments = makeTypeArgumentsFromField(expression), + receiver = expression.receiver, + accessType = AccessType.AlwaysPrivate, + saveVariable = ::variablesSaver + ) + val values: List = makeFlattenedExpressionsWithGivenSafety(node, safe, castedToNotNull(expression.value)) val setterExpressions = instance.makeSetterExpressions(this, values) expression2MfvcNodeInstanceAccessor[setterExpressions] = MfvcNodeInstanceAccessor.Setter(instance, values) +setterExpressions @@ -415,6 +425,12 @@ internal class JvmMultiFieldValueClassLowering( override fun visitClass(declaration: IrClass): IrStatement = declaration 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 thisVar.initializer = irCall(oldPrimaryConstructor).apply { copyTypeAndValueArgumentsFrom(expression) @@ -1252,7 +1268,12 @@ internal class JvmMultiFieldValueClassLowering( 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) { require(it.size == instance.size) { "Incompatible assignment sizes: ${it.size}, ${instance.size}" } instance.makeSetterExpressions(this, it) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedMultiFieldValueClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedMultiFieldValueClassReplacements.kt index b1e124d9845..29612a7fd7c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedMultiFieldValueClassReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedMultiFieldValueClassReplacements.kt @@ -231,7 +231,8 @@ class MemoizedMultiFieldValueClassReplacements( function.isStaticValueClassReplacement || function.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER && function.isAccessor || 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.parent as? IrClass)?.isMultiFieldValueClass == true -> when { diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt index 5e37c9a6699..1c8bbf4cc9c 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPreGenerator.kt @@ -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.addValueParameter 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.util.companionObject import org.jetbrains.kotlin.name.Name @@ -102,7 +103,7 @@ class IrPreGenerator( if (irClass.hasCompanionObjectAsSerializer && irClass.companionObject() ?.findPluginGeneratedMethod(SerialEntityNames.LOAD, compilerContext.afterK2) == null ) return - if (irClass.isValue) return + if (irClass.isSingleFieldValueClass) return if (irClass.findSerializableSyntheticConstructor() != null) return val ctor = irClass.addConstructor { origin = SERIALIZATION_PLUGIN_ORIGIN @@ -121,7 +122,7 @@ class IrPreGenerator( 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() = diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt index 7097ca91685..8c4f5366890 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/IrPredicates.kt @@ -253,4 +253,4 @@ fun IrSimpleType.argumentTypesOrUpperBounds(): List { } internal inline fun IrClass.shouldHaveSpecificSyntheticMethods(functionPresenceChecker: () -> IrSimpleFunction?) = - !isValue && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) + !isSingleFieldValueClass && (isAbstractOrSealedSerializableClass || functionPresenceChecker() != null) diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt index 5e25807f123..8e3d215987b 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializerIrGenerator.kt @@ -634,7 +634,7 @@ open class SerializerIrGenerator( irClass, context ) - serializableDesc.isValue -> SerializerForInlineClassGenerator(irClass, context) + serializableDesc.isSingleFieldValueClass -> SerializerForInlineClassGenerator(irClass, context) else -> SerializerIrGenerator(irClass, context, metadataPlugin) } generator.generate() diff --git a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt index 34d304e3150..85113b8f576 100644 --- a/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt +++ b/plugins/kotlinx-serialization/kotlinx-serialization.k2/src/org/jetbrains/kotlinx/serialization/compiler/fir/checkers/FirSerializationPluginClassChecker.kt @@ -12,7 +12,7 @@ import org.jetbrains.kotlin.diagnostics.* import org.jetbrains.kotlin.fir.FirSession 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.isValueClass +import org.jetbrains.kotlin.fir.analysis.checkers.isSingleFieldValueClass import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClassSymbol import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.* @@ -485,7 +485,7 @@ object FirSerializationPluginClassChecker : FirClassChecker() { context(CheckerContext) @Suppress("IncorrectFormatting") // KTIJ-22227 private val ConeKotlinType.isUnsupportedInlineType: Boolean - get() = isValueClass(session) && !isPrimitiveOrNullablePrimitive + get() = isSingleFieldValueClass(session) && !isPrimitiveOrNullablePrimitive context(CheckerContext) @Suppress("IncorrectFormatting") // KTIJ-22227 diff --git a/plugins/kotlinx-serialization/testData/boxIr/multiFieldValueClasses.kt b/plugins/kotlinx-serialization/testData/boxIr/multiFieldValueClasses.kt new file mode 100644 index 00000000000..bae7358e378 --- /dev/null +++ b/plugins/kotlinx-serialization/testData/boxIr/multiFieldValueClasses.kt @@ -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" +} diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java index bd63653ab9a..75b97803d8f 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationFirBlackBoxTestGenerated.java @@ -93,6 +93,12 @@ public class SerializationFirBlackBoxTestGenerated extends AbstractSerialization 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 @TestMetadata("multimoduleInheritance.kt") public void testMultimoduleInheritance() throws Exception { diff --git a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java index 36966b9de8f..96d4e118bd6 100644 --- a/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java +++ b/plugins/kotlinx-serialization/tests-gen/org/jetbrains/kotlinx/serialization/runners/SerializationIrBoxTestGenerated.java @@ -91,6 +91,12 @@ public class SerializationIrBoxTestGenerated extends AbstractSerializationIrBoxT 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 @TestMetadata("multimoduleInheritance.kt") public void testMultimoduleInheritance() throws Exception {