[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:
committed by
Space Team
parent
90a0b34bf7
commit
c1ac580031
+18
-17
@@ -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)
|
||||
|
||||
+22
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user