[IR] Fix MFVC fields not yet moved from their companions

Signed-off-by: Evgeniy.Zhelenskiy <Evgeniy.Zhelenskiy@jetbrains.com>

#KT-1179
This commit is contained in:
Evgeniy.Zhelenskiy
2023-03-28 21:26:36 +02:00
committed by Space Team
parent 90a0b34bf7
commit c1ac580031
6 changed files with 104 additions and 66 deletions
@@ -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<IrAnonymousInitializer>()
val typeArguments = makeTypeParameterSubstitutionMap(mfvc, primaryConstructorImpl)
if (!mfvc.isKotlinExternalStub()) {
val initializersBlocks = rootMfvcNode.mfvc.declarations.filterIsInstance<IrAnonymousInitializer>()
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)
@@ -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<IrClass, MutableSet<IrField>>()
fun getFieldsToRemove(clazz: IrClass): Set<IrField> = fieldsToRemove[clazz] ?: emptySet()
fun addFieldToRemove(clazz: IrClass, field: IrField) {
fieldsToRemove.getOrPut(clazz) { ConcurrentHashMap<IrField, Unit>().keySet(Unit) }.add(field)
fieldsToRemove.getOrPut(clazz) { ConcurrentHashMap<IrField, Unit>().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)
}
}
@@ -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()}")
}
}
@@ -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<NameableMfvcNode>,
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()}" }
}
@@ -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<LeafMfvcNode>,
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))
@@ -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 {