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 59cd9e9bed4..790e44664fb 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 @@ -428,7 +428,7 @@ internal class JvmMultiFieldValueClassLowering( // `takeIf` is a workaround for double addition problem: user-defined typed equals is already defined in the class rootNode.boxMethod, rootNode.specializedEqualsMethod.takeIf { rootNode.createdNewSpecializedEqualsMethod } ) - rootNode.replacePrimaryMultiFieldValueClassConstructor() + replacePrimaryMultiFieldValueClassConstructor(rootNode) replaceMfvcStaticFields(declaration) } @@ -702,31 +702,32 @@ internal class JvmMultiFieldValueClassLowering( } } - private fun RootMfvcNode.replacePrimaryMultiFieldValueClassConstructor() { - val rootMfvcNode = this - mfvc.declarations.removeIf { it is IrConstructor && it.isPrimary } - mfvc.declarations += listOf(newPrimaryConstructor, primaryConstructorImpl) + private fun replacePrimaryMultiFieldValueClassConstructor(rootMfvcNode: RootMfvcNode) { + rootMfvcNode.mfvc.declarations.removeIf { it is IrConstructor && it.isPrimary } + val newPrimaryConstructor = rootMfvcNode.newPrimaryConstructor + rootMfvcNode.throwWhenNotExternalIsNull(newPrimaryConstructor) + val primaryConstructorImpl = rootMfvcNode.primaryConstructorImpl + rootMfvcNode.throwWhenNotExternalIsNull(primaryConstructorImpl) + rootMfvcNode.mfvc.declarations += listOf(newPrimaryConstructor, primaryConstructorImpl) - val initializersBlocks = mfvc.declarations.filterIsInstance() - val typeArguments = makeTypeParameterSubstitutionMap(mfvc, primaryConstructorImpl) - if (!mfvc.isKotlinExternalStub()) { + val initializersBlocks = rootMfvcNode.mfvc.declarations.filterIsInstance() + val typeArguments = makeTypeParameterSubstitutionMap(rootMfvcNode.mfvc, primaryConstructorImpl) + if (!rootMfvcNode.mfvc.isKotlinExternalStub()) { + val oldPrimaryConstructor = rootMfvcNode.oldPrimaryConstructor + rootMfvcNode.throwWhenNotExternalIsNull(oldPrimaryConstructor) primaryConstructorImpl.body = context.createJvmIrBuilder(primaryConstructorImpl.symbol).irBlockBody { - val mfvcNodeInstance = - ValueDeclarationMfvcNodeInstance(rootMfvcNode, typeArguments, primaryConstructorImpl.valueParameters) - valueDeclarationsRemapper.registerReplacement( - oldPrimaryConstructor.constructedClass.thisReceiver!!, - mfvcNodeInstance - ) + val mfvcNodeInstance = ValueDeclarationMfvcNodeInstance(rootMfvcNode, typeArguments, primaryConstructorImpl.valueParameters) + valueDeclarationsRemapper.registerReplacement(oldPrimaryConstructor.constructedClass.thisReceiver!!, mfvcNodeInstance) for (initializer in initializersBlocks) { +irBlock { for (stmt in initializer.body.statements) { - +stmt.patchDeclarationParents(primaryConstructorImpl) // transformation is done later + +stmt.patchDeclarationParents(rootMfvcNode.primaryConstructorImpl) // transformation is done later } } } } } - mfvc.declarations.removeIf { it is IrAnonymousInitializer } + rootMfvcNode.mfvc.declarations.removeIf { it is IrAnonymousInitializer } } private fun IrBlock.hasLambdaLikeOrigin() = origin == IrStatementOrigin.LAMBDA || origin == IrStatementOrigin.ANONYMOUS_FUNCTION @@ -1274,7 +1275,7 @@ internal class JvmMultiFieldValueClassLowering( for ((subnode, argument) in rootNode.subnodes zip oldArguments) { flattenExpressionTo(argument, instance[subnode.name]!!) } - +irCall(rootNode.primaryConstructorImpl).apply { + +irCall(rootNode.primaryConstructorImpl.let { rootNode.throwWhenNotExternalIsNull(it); it }).apply { copyTypeArgumentsFrom(expression) val flattenedGetterExpressions = instance.makeFlattenedGetterExpressions(this@flattenExpressionTo, irCurrentClass, ::registerPossibleExtraBoxUsage) 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 b8c54aa3a76..805fe2b104c 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 @@ -325,20 +325,34 @@ class MemoizedMultiFieldValueClassReplacements( createIntermediateNodeForMfvcPropertyOfRegularClass(parent, context, property) } + private fun IrField.withAddedStaticReplacementIfNeeded(): IrField { + val property = this.correspondingPropertySymbol?.owner ?: return this + val replacementField = context.cachedDeclarations.getStaticBackingField(property) ?: return this + property.backingField = replacementField + return replacementField + } + + private fun IrProperty.withAddedStaticReplacementIfNeeded(): IrProperty { + val replacementField = context.cachedDeclarations.getStaticBackingField(this) ?: return this + backingField = replacementField + return this + } + private val fieldsToRemove = ConcurrentHashMap>() fun getFieldsToRemove(clazz: IrClass): Set = fieldsToRemove[clazz] ?: emptySet() fun addFieldToRemove(clazz: IrClass, field: IrField) { - fieldsToRemove.getOrPut(clazz) { ConcurrentHashMap().keySet(Unit) }.add(field) + fieldsToRemove.getOrPut(clazz) { ConcurrentHashMap().keySet(Unit) }.add(field.withAddedStaticReplacementIfNeeded()) } fun getMfvcFieldNode(field: IrField): NameableMfvcNode? { - val parent = field.parent - val property = field.correspondingPropertySymbol?.owner + val realField = field.withAddedStaticReplacementIfNeeded() + val parent = realField.parent + val property = realField.correspondingPropertySymbol?.owner return when { property?.isDelegated == false -> getMfvcPropertyNode(property) parent !is IrClass -> null - !field.type.needsMfvcFlattening() -> null - else -> getMfvcStandaloneFieldNodeImpl(field) + !realField.type.needsMfvcFlattening() -> null + else -> getMfvcStandaloneFieldNodeImpl(realField) } } @@ -364,10 +378,11 @@ class MemoizedMultiFieldValueClassReplacements( fun getMfvcPropertyNode(property: IrProperty): NameableMfvcNode? { val parent = property.parent + val realProperty = property.withAddedStaticReplacementIfNeeded() return when { parent !is IrClass -> null - useRootNode(parent, property) -> getRootMfvcNode(parent)[property.name] - else -> getRegularClassMfvcPropertyNode(property) + useRootNode(parent, realProperty) -> getRootMfvcNode(parent)[realProperty.name] + else -> getRegularClassMfvcPropertyNode(realProperty) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedValueClassAbstractReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedValueClassAbstractReplacements.kt index 8304a1d9cde..14319deecb1 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedValueClassAbstractReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedValueClassAbstractReplacements.kt @@ -85,32 +85,32 @@ abstract class MemoizedValueClassAbstractReplacements( if (function is IrSimpleFunction) { val propertySymbol = function.correspondingPropertySymbol if (propertySymbol != null) { + val oldProperty = propertySymbol.owner val property = propertyMap.getOrPut(propertySymbol) { irFactory.buildProperty { - name = propertySymbol.owner.name - updateFrom(propertySymbol.owner) + name = oldProperty.name + updateFrom(oldProperty) }.apply { - parent = propertySymbol.owner.parent - copyAttributes(propertySymbol.owner) - annotations = propertySymbol.owner.annotations + parent = oldProperty.parent + copyAttributes(oldProperty) + annotations = oldProperty.annotations // In case this property is declared in an object in another file which is not yet lowered, its backing field will // be made static later. We have to handle it here though, because this new property will be saved to the cache // and reused when lowering the same call in all subsequent files, which would be incorrect if it was not lowered. - val existingBackingField = propertySymbol.owner.backingField - val skipExistingBackingField = existingBackingField != null && with(context.multiFieldValueClassReplacements) { - getMfvcFieldNode(existingBackingField) - existingBackingField in getFieldsToRemove(propertySymbol.owner.parentAsClass) - } - if (!skipExistingBackingField) { - backingField = context.cachedDeclarations.getStaticBackingField(propertySymbol.owner) - ?: existingBackingField + val newBackingField = context.cachedDeclarations.getStaticBackingField(oldProperty) ?: oldProperty.backingField + if (newBackingField != null) { + context.multiFieldValueClassReplacements.getMfvcFieldNode(newBackingField) + val fieldsToRemove = context.multiFieldValueClassReplacements.getFieldsToRemove(oldProperty.parentAsClass) + if (newBackingField !in fieldsToRemove) { + backingField = newBackingField + } } } } correspondingPropertySymbol = property.symbol when (function) { - propertySymbol.owner.getter -> property.getter = this - propertySymbol.owner.setter -> property.setter = this + oldProperty.getter -> property.getter = this + oldProperty.setter -> property.setter = this else -> error("Orphaned property getter/setter: ${function.render()}") } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNode.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNode.kt index 5d615e32e52..4617ab7108c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNode.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNode.kt @@ -305,8 +305,8 @@ private fun requireSameClasses(vararg classes: IrClass?) { } } -private fun requireSameSizes(vararg sizes: Int) { - require(sizes.asList().zipWithNext { a, b -> a == b }.all { it }) { +private fun requireSameSizes(vararg sizes: Int?) { + require(sizes.asSequence().filterNotNull().distinct().count() == 1) { "Found different sizes: ${sizes.joinToString()}" } } @@ -436,9 +436,9 @@ fun IrSimpleFunction.getGetterField(): IrField? { class RootMfvcNode internal constructor( val mfvc: IrClass, subnodes: List, - val oldPrimaryConstructor: IrConstructor, - val newPrimaryConstructor: IrConstructor, - val primaryConstructorImpl: IrSimpleFunction, + val oldPrimaryConstructor: IrConstructor?, + val newPrimaryConstructor: IrConstructor?, + val primaryConstructorImpl: IrSimpleFunction?, override val boxMethod: IrSimpleFunction, val specializedEqualsMethod: IrSimpleFunction, val createdNewSpecializedEqualsMethod: Boolean, @@ -454,48 +454,48 @@ class RootMfvcNode internal constructor( init { require(type.needsMfvcFlattening()) { "MFVC type expected but got: ${type.render()}" } - for (constructor in listOf(oldPrimaryConstructor, newPrimaryConstructor)) { + for (constructor in listOfNotNull(oldPrimaryConstructor, newPrimaryConstructor)) { require(constructor.isPrimary) { "Expected a primary constructor but got:\n${constructor.dump()}" } } requireSameClasses( mfvc, - oldPrimaryConstructor.parentAsClass, - newPrimaryConstructor.parentAsClass, - primaryConstructorImpl.parentAsClass, + oldPrimaryConstructor?.parentAsClass, + newPrimaryConstructor?.parentAsClass, + primaryConstructorImpl?.parentAsClass, boxMethod.parentAsClass, specializedEqualsMethod.parentAsClass, - oldPrimaryConstructor.constructedClass, - newPrimaryConstructor.constructedClass, + oldPrimaryConstructor?.constructedClass, + newPrimaryConstructor?.constructedClass, boxMethod.returnType.erasedUpperBound, ) - require(primaryConstructorImpl.returnType.isUnit()) { - "Constructor-impl must return Unit but returns ${primaryConstructorImpl.returnType.render()}" + require(primaryConstructorImpl == null || primaryConstructorImpl.returnType.isUnit()) { + "Constructor-impl must return Unit but returns ${primaryConstructorImpl!!.returnType.render()}" } require(specializedEqualsMethod.returnType.isBoolean()) { - "Specialized equals method must return Boolean but returns ${primaryConstructorImpl.returnType.render()}" + "Specialized equals method must return Boolean but returns ${specializedEqualsMethod.returnType.render()}" } - require(oldPrimaryConstructor.typeParameters.isEmpty() && newPrimaryConstructor.typeParameters.isEmpty()) { + require(oldPrimaryConstructor?.typeParameters.isNullOrEmpty() && newPrimaryConstructor?.typeParameters.isNullOrEmpty()) { "Constructors do not support type parameters yet" } requireSameSizes( mfvc.typeParameters.size, boxMethod.typeParameters.size, - primaryConstructorImpl.typeParameters.size, + primaryConstructorImpl?.typeParameters?.size, ) require(specializedEqualsMethod.typeParameters.isEmpty()) { "Specialized equals method must not contain type parameters but has ${specializedEqualsMethod.typeParameters.map { it.defaultType.render() }}" } - requireSameSizes(oldPrimaryConstructor.valueParameters.size, subnodes.size) + oldPrimaryConstructor?.let { requireSameSizes(it.valueParameters.size, subnodes.size) } requireSameSizes( leavesCount, - newPrimaryConstructor.valueParameters.size, - primaryConstructorImpl.valueParameters.size, + newPrimaryConstructor?.valueParameters?.size, + primaryConstructorImpl?.valueParameters?.size, boxMethod.valueParameters.size, ) require(specializedEqualsMethod.valueParameters.size == 1) { "Specialized equals method must contain single value parameter but has\n${specializedEqualsMethod.valueParameters.joinToString("\n") { it.dump() }}" } - for (function in listOf(oldPrimaryConstructor, newPrimaryConstructor, primaryConstructorImpl, boxMethod, specializedEqualsMethod)) { + for (function in listOfNotNull(oldPrimaryConstructor, newPrimaryConstructor, primaryConstructorImpl, boxMethod, specializedEqualsMethod)) { require(function.extensionReceiverParameter == null) { "Extension receiver is not expected for ${function.render()}" } require(function.contextReceiverParametersCount == 0) { "Context receivers are not expected for ${function.render()}" } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeFactory.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeFactory.kt index 6a8657530ab..cf837442e6b 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeFactory.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeFactory.kt @@ -33,6 +33,8 @@ import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.util.OperatorNameConventions +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun createLeafMfvcNode( parent: IrClass, @@ -83,6 +85,22 @@ fun createLeafMfvcNode( fun IrClass.isKotlinExternalStub() = origin == IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB +@OptIn(ExperimentalContracts::class) +private fun IrClass.throwWhenNotExternalIsNull(value: Any?) { + contract { + returns() implies (value != null) + } + if (value == null) throw IllegalStateException("$name is not external but has no primary constructor:\n${dump()}") +} + +@OptIn(ExperimentalContracts::class) +fun RootMfvcNode.throwWhenNotExternalIsNull(value: Any?) { + contract { + returns() implies (value != null) + } + mfvc.throwWhenNotExternalIsNull(value) +} + private fun makeUnboxMethod( context: JvmBackendContext, fullMethodName: Name, @@ -334,7 +352,7 @@ private fun IrProperty.isStatic(currentContainer: IrDeclarationContainer) = fun getRootNode(context: JvmBackendContext, mfvc: IrClass): RootMfvcNode { require(mfvc.isMultiFieldValueClass) { "${mfvc.defaultType.render()} does not require flattening" } - val oldPrimaryConstructor = mfvc.primaryConstructor!! + val oldPrimaryConstructor = mfvc.primaryConstructor val oldFields = mfvc.declarations.mapNotNull { it as? IrField ?: (it as? IrProperty)?.backingField }.filter { !it.isStatic } val representation = mfvc.multiFieldValueClassRepresentation!! val properties = collectPropertiesAfterLowering(mfvc, context).associateBy { it.isStatic(mfvc) to it.name } @@ -344,8 +362,10 @@ fun getRootNode(context: JvmBackendContext, mfvc: IrClass): RootMfvcNode { val leaves = subnodes.leaves val fields = subnodes.fields - val newPrimaryConstructor = makeMfvcPrimaryConstructor(context, oldPrimaryConstructor, mfvc, leaves, fields) - val primaryConstructorImpl = makePrimaryConstructorImpl(context, oldPrimaryConstructor, mfvc, leaves, subnodes) + val newPrimaryConstructor = oldPrimaryConstructor?.let { makeMfvcPrimaryConstructor(context, it, mfvc, leaves, fields) } + val primaryConstructorImpl = oldPrimaryConstructor?.let { + makePrimaryConstructorImpl(context, it, mfvc, leaves, subnodes) + } val boxMethod = makeBoxMethod(context, mfvc, leaves, newPrimaryConstructor) val customEqualsAny = mfvc.functions.singleOrNull { @@ -408,7 +428,7 @@ private fun makeBoxMethod( context: JvmBackendContext, mfvc: IrClass, leaves: List, - newPrimaryConstructor: IrConstructor + newPrimaryConstructor: IrConstructor?, ) = context.irFactory.buildFun { name = Name.identifier(KotlinTypeMapper.BOX_JVM_METHOD_NAME) origin = JvmLoweredDeclarationOrigin.SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER @@ -423,6 +443,7 @@ private fun makeBoxMethod( val parameters = leaves.map { leaf -> addValueParameter(leaf.fullFieldName, leaf.type.substitute(mapping)) } if (!mfvc.isKotlinExternalStub()) { body = with(context.createJvmIrBuilder(this.symbol)) { + mfvc.throwWhenNotExternalIsNull(newPrimaryConstructor) irExprBody(irCall(newPrimaryConstructor).apply { for ((index, parameter) in parameters.withIndex()) { putValueArgument(index, irGet(parameter)) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeInstance.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeInstance.kt index e233e92671c..337f79dda75 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeInstance.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MfvcNodeInstance.kt @@ -226,18 +226,19 @@ class ReceiverBasedMfvcNodeInstance( } override fun makeGetterExpression(scope: IrBuilderWithScope, currentClass: IrClass, registerPossibleExtraBoxCreation: () -> Unit): IrExpression = with(scope) { + fun makeFieldRead(field: IrField) = irGetField(if (field.isStatic) null else makeReceiverCopy(), field) when { node is RootMfvcNode -> makeReceiverCopy()!! node is LeafMfvcNode && node.hasPureUnboxMethod && canUsePrivateAccess(node, currentClass) && fields != null -> - irGetField(makeReceiverCopy(), fields.single()) + makeFieldRead(fields.single()) node is LeafMfvcNode && accessType == AccessType.UseFields -> { require(fields != null) { "Invalid getter to $node" } - irGetField(makeReceiverCopy(), fields.single()) + makeFieldRead(fields.single()) } node is IntermediateMfvcNode && accessType == AccessType.UseFields -> { require(fields != null) { "Invalid getter to $node" } node.makeBoxedExpression( - this, typeArguments, fields.map { irGetField(makeReceiverCopy(), it) }, registerPossibleExtraBoxCreation + this, typeArguments, fields.map(::makeFieldRead), registerPossibleExtraBoxCreation ) } unboxMethod != null -> irCall(unboxMethod).apply {