diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt index 5b18f7bd2bb..816ac95141a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/BoxingInterpreter.kt @@ -301,14 +301,16 @@ private fun MethodInsnNode.isInlineClassBoxingMethodDescriptor(state: Generation if (name != KotlinTypeMapper.BOX_JVM_METHOD_NAME) return false val ownerType = Type.getObjectType(owner) - return desc == Type.getMethodDescriptor(ownerType, unboxedTypeOfInlineClass(ownerType, state)) + val unboxedType = unboxedTypeOfInlineClass(ownerType, state) ?: return false + return desc == Type.getMethodDescriptor(ownerType, unboxedType) } private fun MethodInsnNode.isInlineClassUnboxingMethodDescriptor(state: GenerationState): Boolean { if (name != KotlinTypeMapper.UNBOX_JVM_METHOD_NAME) return false val ownerType = Type.getObjectType(owner) - return desc == Type.getMethodDescriptor(unboxedTypeOfInlineClass(ownerType, state)) + val unboxedType = unboxedTypeOfInlineClass(ownerType, state) ?: return false + return desc == Type.getMethodDescriptor(unboxedType) } fun AbstractInsnNode.isNextMethodCallOfProgressionIterator(values: List) = diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index 54b8e74715c..4644f9a722f 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -50194,6 +50194,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT public void testEquality() throws Exception { runTest("compiler/testData/codegen/box/valueClasses/equality.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); } + + @Test + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("compiler/testData/codegen/box/valueClasses/simple.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); + } } @Nested diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/AnnotationCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/AnnotationCodegen.kt index b3a89cfc0dd..caf15c4b8f7 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/AnnotationCodegen.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/AnnotationCodegen.kt @@ -346,6 +346,7 @@ abstract class AnnotationCodegen( declaration.origin.isSynthetic -> true declaration.origin == JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD || + declaration.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD || declaration.origin == IrDeclarationOrigin.GENERATED_SAM_IMPLEMENTATION -> true else -> diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 3e2253e62bd..cae3d0ae2b8 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -294,6 +294,7 @@ class ExpressionCodegen( (irFunction is IrConstructor && irFunction.parentAsClass.isAnonymousObject) || // TODO: Implement this as a lowering, so that we can more easily exclude generated methods. irFunction.origin == JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD || + irFunction.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD || // Although these are accessible from Java, the functions they bridge to already have the assertions. irFunction.origin == IrDeclarationOrigin.BRIDGE_SPECIAL || irFunction.origin == JvmLoweredDeclarationOrigin.SUPER_INTERFACE_METHOD_BRIDGE || diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt index 4233c25df5f..1333b221491 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/HashCode.kt @@ -37,9 +37,10 @@ object HashCode : IntrinsicMethod() { val target = context.state.target when { irFunction.origin == JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD || + irFunction.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD || irFunction.origin == IrDeclarationOrigin.GENERATED_DATA_CLASS_MEMBER || irFunction.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER -> { - // TODO generate or lower IR for data class / inline class 'hashCode'? + // TODO generate or lower IR for data class / value class 'hashCode'? DescriptorAsmUtil.genHashCode(mv, mv, receiverType, target) } target >= JvmTarget.JVM_1_8 && AsmUtil.isPrimitive(receiverJvmType) -> { diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index d7ffe0f2f25..ca0fbf7d257 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -309,6 +309,7 @@ private val jvmFilePhases = listOf( forLoopsPhase, collectionStubMethodLowering, singleAbstractMethodPhase, + jvmMultiFieldValueClassPhase, jvmInlineClassPhase, tailrecPhase, // makePatchParentsPhase(), diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt index eb099727bfe..62382ec8ef8 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt @@ -112,7 +112,7 @@ internal val bridgePhase = makeIrFilePhase( ::BridgeLowering, name = "Bridge", description = "Generate bridges", - prerequisite = setOf(jvmInlineClassPhase) + prerequisite = setOf(jvmInlineClassPhase, jvmMultiFieldValueClassPhase) ) internal class BridgeLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoid() { diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt index 500a8c65f83..7ea5df3e87e 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt @@ -9,8 +9,6 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.lower.irBlockBody import org.jetbrains.kotlin.backend.common.lower.loops.forLoopsPhase import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase -import org.jetbrains.kotlin.backend.common.pop -import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.jvm.* import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound import org.jetbrains.kotlin.builtins.StandardNames @@ -24,6 +22,7 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.transformStatement import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classOrNull @@ -43,7 +42,9 @@ val jvmInlineClassPhase = makeIrFilePhase( // Standard library replacements are done on the unmangled names for UInt and ULong classes. // Collection stubs may require mangling by inline class rules. // SAM wrappers may require mangling for fun interfaces with inline class parameters - prerequisite = setOf(forLoopsPhase, jvmBuiltInsPhase, collectionStubMethodLowering, singleAbstractMethodPhase), + prerequisite = setOf( + forLoopsPhase, jvmBuiltInsPhase, collectionStubMethodLowering, singleAbstractMethodPhase, jvmMultiFieldValueClassPhase + ), ) /** @@ -58,11 +59,56 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass override val replacements: MemoizedValueClassAbstractReplacements get() = context.inlineClassReplacements + private val valueMap = mutableMapOf() + + override fun addBindingsFor(original: IrFunction, replacement: IrFunction) { + for ((param, newParam) in original.explicitParameters.zip(replacement.explicitParameters)) { + valueMap[param.symbol] = newParam + } + } + override fun IrClass.isSpecificLoweringLogicApplicable(): Boolean = isSingleFieldValueClass - override fun IrFunction.isSpecificFieldGetter(): Boolean = isInlineClassFieldGetter + override fun IrFunction.isFieldGetterToRemove(): Boolean = isInlineClassFieldGetter + override fun visitClassNew(declaration: IrClass): IrStatement { + // The arguments to the primary constructor are in scope in the initializers of IrFields. - override fun addJvmInlineAnnotation(valueClass: IrClass) { + declaration.primaryConstructor?.let { + replacements.getReplacementFunction(it)?.let { replacement -> addBindingsFor(it, replacement) } + } + + declaration.transformDeclarationsFlat { memberDeclaration -> + if (memberDeclaration is IrFunction) { + withinScope(memberDeclaration) { + transformFunctionFlat(memberDeclaration) + } + } else { + memberDeclaration.accept(this, null) + null + } + } + + if (declaration.isSpecificLoweringLogicApplicable()) { + handleSpecificNewClass(declaration) + } + + return declaration + } + + override fun handleSpecificNewClass(declaration: IrClass) { + val irConstructor = declaration.primaryConstructor!! + // The field getter is used by reflection and cannot be removed here unless it is internal. + declaration.declarations.removeIf { + it == irConstructor || (it is IrFunction && it.isFieldGetterToRemove() && !it.visibility.isPublicAPI) + } + buildPrimaryInlineClassConstructor(declaration, irConstructor) + buildBoxFunction(declaration) + buildUnboxFunction(declaration) + buildSpecializedEqualsMethod(declaration) + addJvmInlineAnnotation(declaration) + } + + fun addJvmInlineAnnotation(valueClass: IrClass) { if (valueClass.hasAnnotation(JVM_INLINE_ANNOTATION_FQ_NAME)) return val constructor = context.ir.symbols.jvmInlineAnnotation.constructors.first() valueClass.annotations = valueClass.annotations + IrConstructorCallImpl.fromSymbolOwner( @@ -71,20 +117,10 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass ) } - override fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List { - replacement.valueParameters.forEach { - it.transformChildrenVoid() - it.defaultValue?.patchDeclarationParents(replacement) - } - allScopes.push(createScope(function)) - replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement) - allScopes.pop() - replacement.copyAttributes(function) - - // Don't create a wrapper for functions which are only used in an unboxed context - if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null) - return listOf(replacement) - + override fun createBridgeFunction( + function: IrSimpleFunction, + replacement: IrSimpleFunction + ): IrSimpleFunction { val bridgeFunction = createBridgeDeclaration( function, when { @@ -113,8 +149,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass } else { createBridgeBody(bridgeFunction, replacement) } - - return listOf(replacement, bridgeFunction) + return bridgeFunction } private fun IrSimpleFunction.signatureRequiresMangling() = @@ -153,7 +188,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass // Secondary constructors for boxed types get translated to static functions returning // unboxed arguments. We remove the original constructor. // Primary constructors' case is handled at the start of transformFunctionFlat - override fun transformConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List { + override fun transformSecondaryConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List { replacement.valueParameters.forEach { it.transformChildrenVoid() } replacement.body = context.createIrBuilder(replacement.symbol, replacement.startOffset, replacement.endOffset).irBlockBody( replacement @@ -211,12 +246,6 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass return listOf(replacement) } - private fun typedArgumentList(function: IrFunction, expression: IrMemberAccessExpression<*>) = - listOfNotNull( - function.dispatchReceiverParameter?.let { it to expression.dispatchReceiver }, - function.extensionReceiverParameter?.let { it to expression.extensionReceiver } - ) + function.valueParameters.map { it to expression.getValueArgument(it.index) } - private fun IrMemberAccessExpression<*>.buildReplacement( originalFunction: IrFunction, original: IrMemberAccessExpression<*>, @@ -420,7 +449,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass return super.visitSetValue(expression) } - override fun buildPrimaryValueClassConstructor(valueClass: IrClass, irConstructor: IrConstructor) { + fun buildPrimaryInlineClassConstructor(valueClass: IrClass, irConstructor: IrConstructor) { // Add the default primary constructor valueClass.addConstructor { updateFrom(irConstructor) @@ -465,7 +494,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass valueClass.declarations += function } - override fun buildBoxFunction(valueClass: IrClass) { + fun buildBoxFunction(valueClass: IrClass) { val function = context.inlineClassReplacements.getBoxFunction(valueClass) with(context.createIrBuilder(function.symbol)) { function.body = irExprBody( @@ -478,10 +507,6 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass valueClass.declarations += function } - override fun buildUnboxFunctions(valueClass: IrClass) { - buildUnboxFunction(valueClass) - } - private fun buildUnboxFunction(irClass: IrClass) { val function = context.inlineClassReplacements.getUnboxFunction(irClass) val field = getInlineClassBackingField(irClass) @@ -494,7 +519,7 @@ private class JvmInlineClassLowering(context: JvmBackendContext) : JvmValueClass irClass.declarations += function } - override fun buildSpecializedEqualsMethod(valueClass: IrClass) { + fun buildSpecializedEqualsMethod(valueClass: IrClass) { val function = context.inlineClassReplacements.getSpecializedEqualsMethod(valueClass, context.irBuiltIns) val left = function.valueParameters[0] val right = function.valueParameters[1] 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 3342d36be91..acc03c2a4b3 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 @@ -5,45 +5,398 @@ package org.jetbrains.kotlin.backend.jvm.lower -import org.jetbrains.kotlin.backend.jvm.JvmBackendContext -import org.jetbrains.kotlin.backend.jvm.MemoizedValueClassAbstractReplacements -import org.jetbrains.kotlin.backend.jvm.isMultiFieldValueClassFieldGetter +import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter +import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase +import org.jetbrains.kotlin.backend.jvm.* +import org.jetbrains.kotlin.backend.jvm.MemoizedMultiFieldValueClassReplacements.ValueParameterTemplate +import org.jetbrains.kotlin.backend.jvm.MultiFieldValueClassSpecificDeclarations.ImplementationAgnostic +import org.jetbrains.kotlin.backend.jvm.MultiFieldValueClassSpecificDeclarations.VirtualProperty +import org.jetbrains.kotlin.backend.jvm.MultiFieldValueClassTree.InternalNode +import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +import org.jetbrains.kotlin.backend.jvm.ir.isMultiFieldValueClassType +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.addGetter +import org.jetbrains.kotlin.ir.builders.declarations.buildField +import org.jetbrains.kotlin.ir.builders.declarations.buildProperty import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.IrValueSymbol +import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl +import org.jetbrains.kotlin.ir.transformStatement +import org.jetbrains.kotlin.ir.types.classOrNull +import org.jetbrains.kotlin.ir.types.isNullable +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.addToStdlib.popLast + +val jvmMultiFieldValueClassPhase = makeIrFilePhase( + ::JvmMultiFieldValueClassLowering, + name = "Multi-field Value Classes", + description = "Lower multi-field value classes", + // Collection stubs may require mangling by multi-field value class rules. + // SAM wrappers may require mangling for fun interfaces with multi-field value class parameters + prerequisite = setOf(collectionStubMethodLowering, singleAbstractMethodPhase), +) private class JvmMultiFieldValueClassLowering(context: JvmBackendContext) : JvmValueClassAbstractLowering(context) { - override val replacements: MemoizedValueClassAbstractReplacements + + private open inner class ValueDeclarationsRemapper { + private val symbol2getter = mutableMapOf>() + private val symbol2setters = mutableMapOf?>>() + private val knownExpressions = mutableMapOf>() + + fun remapSymbol(original: IrValueSymbol, replacement: IrValueDeclaration) { + symbol2getter[original] = { irGet(replacement) } + symbol2setters[original] = listOf(if (replacement.isAssignable) { _, value -> irSet(replacement, value) } else null) + } + + fun remapSymbol(original: IrValueSymbol, unboxed: List>): Unit = + remapSymbol(original, replacements.getDeclarations(original.owner.type.erasedUpperBound)!!.ImplementationAgnostic(unboxed)) + + fun remapSymbol(original: IrValueSymbol, unboxed: ImplementationAgnostic) { + symbol2getter[original] = { + unboxed.boxedExpression(this, Unit).also { irExpression -> knownExpressions[irExpression] = unboxed } + } + symbol2setters[original] = unboxed.virtualFields.map { it.assigner } + } + + fun IrBuilderWithScope.getter(original: IrValueSymbol): IrExpression? = + symbol2getter[original]?.invoke(this, Unit) + + fun setter(original: IrValueSymbol): List?>? = symbol2setters[original] + + fun implementationAgnostic(expression: IrExpression): ImplementationAgnostic? = knownExpressions[expression] + + fun IrBuilderWithScope.subfield(expression: IrExpression, name: Name): IrExpression? = + implementationAgnostic(expression)?.get(name)?.let { (expressionGenerator, representation) -> + val res = expressionGenerator(this, Unit) + representation?.let { knownExpressions[res] = it } + res + } + + /** + * Register value declaration instead of singular expressions when possible + */ + fun registerExpression(getter: IrExpression, representation: ImplementationAgnostic) { + knownExpressions[getter] = representation + } + } + + private val valueDeclarationsRemapper = ValueDeclarationsRemapper() + + private val regularClassMFVCPropertyMainGetters: MutableMap> = mutableMapOf() + private val regularClassMFVCPropertyNextGetters: MutableMap> = mutableMapOf() + private val regularClassMFVCPropertyFieldsMapping: MutableMap> = mutableMapOf() + private val regularClassMFVCPropertyNodes: MutableMap> = mutableMapOf() + + override val replacements get() = context.multiFieldValueClassReplacements override fun IrClass.isSpecificLoweringLogicApplicable(): Boolean = isMultiFieldValueClass - override fun IrFunction.isSpecificFieldGetter(): Boolean = isMultiFieldValueClassFieldGetter + override fun IrFunction.isFieldGetterToRemove(): Boolean = isMultiFieldValueClassOriginalFieldGetter - override fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List { - TODO() + override fun visitClassNew(declaration: IrClass): IrStatement { + + if (declaration.isSpecificLoweringLogicApplicable()) { + handleSpecificNewClass(declaration) + } else { + handleNonSpecificNewClass(declaration) + } + + declaration.transformDeclarationsFlat { memberDeclaration -> + if (memberDeclaration is IrFunction) { + withinScope(memberDeclaration) { + transformFunctionFlat(memberDeclaration) + } + } else { + memberDeclaration.accept(this, null) + null + } + } + + return declaration } - override fun buildPrimaryValueClassConstructor(valueClass: IrClass, irConstructor: IrConstructor) { - TODO("Not yet implemented") + private fun handleNonSpecificNewClass(declaration: IrClass) { + declaration.primaryConstructor?.let { + replacements.getReplacementRegularClassConstructor(it)?.let { replacement -> addBindingsFor(it, replacement) } + } + val fieldsToReplace = declaration.fields.filter { !it.type.isNullable() && it.type.isMultiFieldValueClassType() }.toList() + + val oldFieldToInitializers: MutableMap = mutableMapOf() + // we need to preserve order of initializations + // todo test it + val newFields: List> = fieldsToReplace.map { oldField -> + val declarations = replacements.getDeclarations(oldField.type.erasedUpperBound)!! + val newFields = declarations.fields.map { sourceField -> + context.irFactory.buildField { + name = Name.guessByFirstCharacter("${oldField.name.asString()}$${sourceField.name.asString()}") + type = sourceField.type + origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + visibility = sourceField.visibility + }.apply { + parent = declaration + initializer + } + } + regularClassMFVCPropertyFieldsMapping[oldField] = newFields + val virtualFields: List> = newFields.map { newField -> + VirtualProperty( + type = newField.type, + makeGetter = { receiver: IrValueDeclaration -> irGetField(irGet(receiver), newField) }, + assigner = { receiver: IrValueDeclaration, value: IrExpression -> irSetField(irGet(receiver), newField, value) }, + ) + } + val implementationAgnostic = declarations.ImplementationAgnostic(virtualFields) + + val property = oldField.correspondingPropertySymbol?.owner + val mainGetter = property?.getter + val mainSetter = property?.setter + property?.backingField = null + if (mainGetter != null) { + if (!property.isDelegated && mainGetter.isDefaultGetter(oldField)) { + mainGetter.origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + regularClassMFVCPropertyMainGetters.getOrPut(declaration) { mutableMapOf() }[oldField.name] = mainGetter + val nodesToGetters = implementationAgnostic.nodeToExpressionGetters.mapValues { (node, exprGen) -> + if (node == declarations.loweringRepresentation) mainGetter else { + context.irFactory.buildProperty { + val nameAsString = "${oldField.name.asString()}$${declarations.nodeFullNames[node]!!.asString()}" + name = Name.guessByFirstCharacter(nameAsString) + origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + visibility = oldField.visibility + }.apply { + parent = declaration + addGetter { + returnType = node.type + origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + }.apply { + createDispatchReceiverParameter() + body = with(context.createIrBuilder(this.symbol)) { + irExprBody(exprGen(this, dispatchReceiverParameter!!)) + } + } + }.getter!!.also { + declaration.declarations.add(it) + } + } + } + regularClassMFVCPropertyNodes.putAll(nodesToGetters.map { (node, getter) -> getter to node }) + for ((node, getter) in nodesToGetters) { + if (node is InternalNode) { + regularClassMFVCPropertyNextGetters[getter] = node.fields.associate { it.name to nodesToGetters[it.node]!! } + } + } + } + } + for (accessor in listOfNotNull(mainGetter, mainSetter)) + accessor.body?.transform(object : IrElementTransformerVoid() { + override fun visitGetField(expression: IrGetField): IrExpression { + if (expression.symbol.owner == oldField) { + require(expression.receiver.let { it is IrGetValue && it.symbol.owner == accessor.dispatchReceiverParameter!! }) { + "Unexpected receiver for IrGetField: ${expression.receiver}" + } + val gettersAndSetters = + newFields.toGettersAndSetters(accessor.dispatchReceiverParameter!!, transformReceiver = true) + val representation = newFields.zip(gettersAndSetters) { newField, (getter, setter) -> + VirtualProperty(newField.type, getter, setter) + } + val fieldRepresentation = declarations.ImplementationAgnostic(representation) + val boxed = fieldRepresentation.boxedExpression( + context.createIrBuilder((currentScope!!.irElement as IrSymbolOwner).symbol), Unit + ) + valueDeclarationsRemapper.registerExpression(boxed, fieldRepresentation) + return boxed + } + return super.visitGetField(expression) + } + }, null) + oldField.initializer?.let { initializer -> + context.irFactory.createAnonymousInitializer( + startOffset = UNDEFINED_OFFSET, + endOffset = UNDEFINED_OFFSET, origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER, + symbol = IrAnonymousInitializerSymbolImpl() + ).apply { + parent = declaration + body = context.createIrBuilder(this.symbol).irBlockBody { + +irBlock { + flattenExpressionTo( + initializer.expression.transform(this@JvmMultiFieldValueClassLowering, null), + newFields.toGettersAndSetters(declaration.thisReceiver!!) + ) + } + } + oldFieldToInitializers[oldField] = this + } + } + newFields + } + for (i in declaration.declarations.indices) { + oldFieldToInitializers[declaration.declarations[i]]?.let { initializer -> + declaration.declarations[i] = initializer + } + } + declaration.declarations.addAll(newFields.flatten()) + declaration.declarations.removeAll(fieldsToReplace) } - override fun buildBoxFunction(valueClass: IrClass) { - TODO("Not yet implemented") + override fun handleSpecificNewClass(declaration: IrClass) { + replacements.setOldFields(declaration, declaration.fields.toList()) + val newDeclarations = replacements.getDeclarations(declaration)!! + if (newDeclarations.valueClass != declaration) error("Unexpected IrClass ${newDeclarations.valueClass} instead of $declaration") + newDeclarations.replaceFields() + newDeclarations.replaceProperties() + newDeclarations.buildPrimaryMultiFieldValueClassConstructor() + newDeclarations.buildBoxFunction() + newDeclarations.buildUnboxFunctions() + newDeclarations.buildSpecializedEqualsMethod() } - override fun buildUnboxFunctions(valueClass: IrClass) { - TODO("Not yet implemented") + override fun transformSecondaryConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List { + replacement.valueParameters.forEach { it.transformChildrenVoid() } + replacement.body = context.createIrBuilder(replacement.symbol).irBlockBody { + val thisVar = irTemporary(irType = replacement.returnType, nameHint = "\$this") + constructor.body?.statements?.forEach { statement -> + +statement.transformStatement(object : IrElementTransformerVoid() { + override fun visitClass(declaration: IrClass): IrStatement = declaration + + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { + val oldPrimaryConstructor = replacements.getDeclarations(constructor.constructedClass)!!.oldPrimaryConstructor + thisVar.initializer = irCall(oldPrimaryConstructor).apply { + copyTypeAndValueArgumentsFrom(expression) + } + return irBlock {} + } + + override fun visitGetValue(expression: IrGetValue): IrExpression = when (expression.symbol.owner) { + constructor.constructedClass.thisReceiver!! -> irGet(thisVar) + else -> super.visitGetValue(expression) + } + + override fun visitReturn(expression: IrReturn): IrExpression { + expression.transformChildrenVoid() + if (expression.returnTargetSymbol != constructor.symbol) + return expression + + return irReturn(irBlock(expression.startOffset, expression.endOffset) { + +expression.value + +irGet(thisVar) + }) + } + }) + } + +irReturn(irGet(thisVar)) + } + .also { addBindingsFor(constructor, replacement) } + .transform(this@JvmMultiFieldValueClassLowering, null) + .patchDeclarationParents(replacement) + return listOf(replacement) } - override fun buildSpecializedEqualsMethod(valueClass: IrClass) { - TODO("Not yet implemented") + private fun MultiFieldValueClassSpecificDeclarations.replaceFields() { + valueClass.declarations.removeIf { it is IrField } + valueClass.declarations += fields + for (field in fields) { + field.parent = valueClass + } } - override fun addJvmInlineAnnotation(valueClass: IrClass) = Unit + private fun MultiFieldValueClassSpecificDeclarations.replaceProperties() { + valueClass.declarations.removeIf { it is IrFunction && it.isFieldGetterToRemove() } + properties.values.forEach { + it.parent = valueClass + } + valueClass.declarations += properties.values.map { it.getter!!.apply { parent = valueClass } } + } - @Suppress("UNUSED_PARAMETER") - override fun transformConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List { - TODO() + override fun createBridgeFunction(function: IrSimpleFunction, replacement: IrSimpleFunction): IrSimpleFunction? { + return null // todo + // todo change return type to non-nullable for base class + } + + override fun addBindingsFor(original: IrFunction, replacement: IrFunction) { + val parametersStructure = replacements.bindingParameterTemplateStructure[original]!! + require(parametersStructure.size == original.explicitParameters.size) { + "Wrong value parameters structure: $parametersStructure" + } + require(parametersStructure.sumOf { it.size } == replacement.explicitParameters.size) { + "Wrong value parameters structure: $parametersStructure" + } + val old2newList = original.explicitParameters.zip( + parametersStructure.scan(0) { partial: Int, templates: List -> partial + templates.size } + .zipWithNext { start: Int, finish: Int -> replacement.explicitParameters.slice(start until finish) } + ) + for ((param, newParamList) in old2newList) { + val single = newParamList.singleOrNull() + if (single != null) { + valueDeclarationsRemapper.remapSymbol(param.symbol, single) + } else { + valueDeclarationsRemapper.remapSymbol(param.symbol, newParamList.map { VirtualProperty(it) }) + } + } + } + + fun MultiFieldValueClassSpecificDeclarations.buildPrimaryMultiFieldValueClassConstructor() { + valueClass.declarations.removeIf { it is IrConstructor && it.isPrimary } + val primaryConstructorReplacements = listOf(primaryConstructor, primaryConstructorImpl) + for (exConstructor in primaryConstructorReplacements) { + exConstructor.parent = valueClass + } + valueClass.declarations += primaryConstructorReplacements + + val initializersStatements = valueClass.declarations.filterIsInstance().flatMap { it.body.statements } + valueDeclarationsRemapper.remapSymbol( + oldPrimaryConstructor.constructedClass.thisReceiver!!.symbol, + primaryConstructorImpl.valueParameters.map { VirtualProperty(it) } + ) + primaryConstructorImpl.body = context.createIrBuilder(primaryConstructorImpl.symbol).irBlockBody { + for (stmt in initializersStatements) { + +stmt.transformStatement(this@JvmMultiFieldValueClassLowering).patchDeclarationParents(primaryConstructorImpl) + } + } + valueClass.declarations.removeIf { it is IrAnonymousInitializer } + } + + fun MultiFieldValueClassSpecificDeclarations.buildBoxFunction() { + boxMethod.body = with(context.createIrBuilder(boxMethod.symbol)) { + irExprBody(irCall(primaryConstructor.symbol).apply { + passTypeArgumentsFrom(boxMethod) + for (i in leaves.indices) { + putValueArgument(i, irGet(boxMethod.valueParameters[i])) + } + }) + } + valueClass.declarations += boxMethod + boxMethod.parent = valueClass + } + + fun MultiFieldValueClassSpecificDeclarations.buildUnboxFunctions() { + valueClass.declarations += unboxMethods + } + + @Suppress("unused") + fun MultiFieldValueClassSpecificDeclarations.buildSpecializedEqualsMethod() { + // todo defaults + specializedEqualsMethod.parent = valueClass + specializedEqualsMethod.body = with(context.createIrBuilder(specializedEqualsMethod.symbol)) { + // TODO: Revisit this once we allow user defined equals methods in inline/multi-field value classes. + leaves.indices.map { + val left = irGet(specializedEqualsMethod.valueParameters[it]) + val right = irGet(specializedEqualsMethod.valueParameters[it + leaves.size]) + irEquals(left, right) + }.reduce { acc, current -> + irCall(context.irBuiltIns.andandSymbol).apply { + putValueArgument(0, acc) + putValueArgument(1, current) + } + }.let { irExprBody(it) } + } + valueClass.declarations += specializedEqualsMethod } override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { @@ -52,27 +405,337 @@ private class JvmMultiFieldValueClassLowering(context: JvmBackendContext) : JvmV } override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { - // todo implement - return super.visitFunctionAccess(expression) + val function = expression.symbol.owner + val replacement = replacements.getReplacementFunction(function) + val currentScope = currentScope!!.irElement as IrDeclaration + return when { + function is IrConstructor && function.isPrimary && function.constructedClass.isMultiFieldValueClass && + currentScope.origin != JvmLoweredDeclarationOrigin.SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER -> { + context.createIrBuilder(currentScope.symbol).irBlock { + val thisReplacement = irTemporary(expression) + +irGet(thisReplacement) + }.transform(this, null) // transform with visitVariable + } + replacement != null -> context.createIrBuilder(currentScope.symbol).irBlock { + buildReplacement(function, expression, replacement) + } + else -> + when (val newConstructor = (function as? IrConstructor)?.let { replacements.getReplacementRegularClassConstructor(it) }) { + null -> super.visitFunctionAccess(expression) + else -> context.createIrBuilder(currentScope.symbol).irBlock { + buildReplacement(function, expression, newConstructor) + } + } + } } override fun visitCall(expression: IrCall): IrExpression { - // todo implement + val callee = expression.symbol.owner + val receiver = expression.dispatchReceiver?.transform(this, null) + val name = callee.correspondingPropertySymbol?.owner?.name + if (callee.isMultiFieldValueClassOriginalFieldGetter && receiver != null) { + with(valueDeclarationsRemapper) { + with(context.createIrBuilder(expression.symbol)) { + subfield(receiver, name!!)?.let { return it } + } + } + } + if (callee.isGetter && name != null && receiver is IrCall) { + val nextFunction = regularClassMFVCPropertyNextGetters[receiver.symbol.owner]?.get(name) + if (nextFunction != null) { + return with(context.createIrBuilder(expression.symbol)) { + irCall(nextFunction).apply { + this.dispatchReceiver = receiver.dispatchReceiver + } + } + } + } + @Suppress("ControlFlowWithEmptyBody") + if (expression.isSpecializedInlineClassEqEq) { + // todo + } return super.visitCall(expression) } - override fun visitGetField(expression: IrGetField): IrExpression { - // todo implement - return super.visitGetField(expression) + private fun makeLeavesGetters(currentGetter: IrSimpleFunction): List? = + when (val node = regularClassMFVCPropertyNodes[currentGetter]) { + null -> null + is MultiFieldValueClassTree.Leaf -> listOf(currentGetter) + is InternalNode -> node.fields.flatMap { makeLeavesGetters(regularClassMFVCPropertyNextGetters[currentGetter]!![it.name]!!)!! } + } + + private fun IrBlockBuilder.buildReplacement( + originalFunction: IrFunction, + original: IrMemberAccessExpression<*>, + replacement: IrFunction + ) { + val parameter2expression = typedArgumentList(originalFunction, original) + val structure = replacements.bindingParameterTemplateStructure[originalFunction]!! + require(parameter2expression.size == structure.size) + require(structure.sumOf { it.size } == replacement.explicitParametersCount) + val newArguments: List = makeNewArguments(parameter2expression.map { (_, argument) -> argument }, structure) + +irCall(replacement.symbol).apply { + copyTypeArgumentsFrom(original) + for ((parameter, argument) in replacement.explicitParameters zip newArguments) { + if (argument == null) continue + putArgument(replacement, parameter, argument.transform(this@JvmMultiFieldValueClassLowering, null)) + } + } } - override fun visitGetValue(expression: IrGetValue): IrExpression { - // todo implement - return super.visitGetValue(expression) + private fun IrBlockBuilder.makeNewArguments( + oldArguments: List, + structure: List> + ): List { + val variables = structure.flatMap { argTemplate -> argTemplate.map { irTemporary(irType = it.type) } } + val subVariables = structure.scan(0) { acc: Int, templates: List -> acc + templates.size } + .zipWithNext().map { (start, finish) -> variables.slice(start until finish) } + return (oldArguments zip subVariables).flatMap { (oldArgument, curSubVariables) -> + val oldArgumentTransformed = oldArgument?.transform(this@JvmMultiFieldValueClassLowering, null) + when { + oldArgumentTransformed == null -> List(curSubVariables.size) { null } + curSubVariables.size == 1 -> listOf(oldArgumentTransformed) + else -> flattenExpressionTo(oldArgumentTransformed, curSubVariables.toGettersAndSetters()) + .let { curSubVariables.map { irGet(it) } } + } + } + } + + // Note that reference equality (x === y) is not allowed on values of inline class type, + // so it is enough to check for eqeq. + private val IrCall.isSpecializedInlineClassEqEq: Boolean + get() = symbol == context.irBuiltIns.eqeqSymbol && + getValueArgument(0)?.type?.classOrNull?.owner?.takeIf { it.isMultiFieldValueClass } != null + + override fun visitGetField(expression: IrGetField): IrExpression { + val field = expression.symbol.owner + val parent = field.parent + return when { + field.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && + parent is IrClass && + parent.multiFieldValueClassRepresentation?.containsPropertyWithName(field.name) == true -> { + val receiver = expression.receiver!!.transform(this, null) + with(valueDeclarationsRemapper) { + with(context.createIrBuilder(expression.symbol)) { + subfield(receiver, field.name) ?: run { + expression.receiver = receiver + expression + } + } + } + } + field.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && !field.type.isNullable() && field.type.isMultiFieldValueClassType() -> { + val getter = regularClassMFVCPropertyMainGetters[expression.receiver!!.type.erasedUpperBound]?.get(field.name) + ?: return super.visitGetField(expression) + with(context.createIrBuilder(expression.symbol)) { + irCall(getter).apply { + dispatchReceiver = expression.receiver!!.transform(this@JvmMultiFieldValueClassLowering, null) + } + } + } + else -> super.visitGetField(expression) + } + } + + override fun visitSetField(expression: IrSetField): IrExpression { + val field = expression.symbol.owner + val replacementFields = regularClassMFVCPropertyFieldsMapping[field] ?: return super.visitSetField(expression) + return context.createIrBuilder(expression.symbol).irBlock { + val thisVar = irTemporary(expression.receiver!!.transform(this@JvmMultiFieldValueClassLowering, null)) + val variables = replacementFields.map { irTemporary(irType = it.type, nameHint = it.name.asString()) } + // We flatten to temp variables because code can throw an exception otherwise and partially update variables + flattenExpressionTo(expression.value.transform(this@JvmMultiFieldValueClassLowering, null), variables.toGettersAndSetters()) + for ((replacementField, variable) in replacementFields zip variables) { + +irSetField(irGet(thisVar), replacementField, irGet(variable)) + } + } + } + + override fun visitGetValue(expression: IrGetValue): IrExpression = with(context.createIrBuilder(expression.symbol)) { + with(valueDeclarationsRemapper) { + getter(expression.symbol) ?: super.visitGetValue(expression) + } } override fun visitSetValue(expression: IrSetValue): IrExpression { - // todo implement - return super.visitSetValue(expression) + val setters = valueDeclarationsRemapper.setter(expression.symbol) ?: return super.visitSetValue(expression) + return context.createIrBuilder(expression.symbol).irBlock { + val declarations = replacements.getDeclarations(expression.symbol.owner.type.erasedUpperBound)!! + val variables = declarations.leaves.map { irTemporary(irType = it.type) } + // We flatten to temp variables because code can throw an exception otherwise and partially update variables + flattenExpressionTo(expression.value.transform(this@JvmMultiFieldValueClassLowering, null), variables.toGettersAndSetters()) + for ((setter, variable) in setters zip variables) { + setter?.invoke(this, Unit, irGet(variable))?.let { +it } + } + } } -} \ No newline at end of file + + override fun visitVariable(declaration: IrVariable): IrStatement { + val initializer = declaration.initializer + if (declaration.type.isMultiFieldValueClassType() && !declaration.type.isNullable()) { + val irClass = declaration.type.erasedUpperBound + val declarations = replacements.getDeclarations(irClass)!! + return context.createIrBuilder((currentScope!!.irElement as IrSymbolOwner).symbol).irComposite { + val variables = declarations.leaves.map { leaf -> + irTemporary( + nameHint = "${declaration.name.asString()}$${declarations.nodeFullNames[leaf]!!.asString()}", + irType = leaf.type, + isMutable = declaration.isVar + ) + } + initializer?.let { + flattenExpressionTo(it, variables.toGettersAndSetters()) + } + valueDeclarationsRemapper.remapSymbol(declaration.symbol, variables.map { VirtualProperty(it) }) + } + } + return super.visitVariable(declaration) + } + + private fun List.toGettersAndSetters() = map { variable -> + Pair, ExpressionSupplier>( + { irGet(variable) }, + { _, value: IrExpression -> irSet(variable, value) } + ) + } + + private fun List.toGettersAndSetters(receiver: IrValueParameter, transformReceiver: Boolean = false) = map { field -> + Pair, ExpressionSupplier>( + { + val initialGetReceiver = irGet(receiver) + val resultReceiver = + if (transformReceiver) initialGetReceiver.transform(this@JvmMultiFieldValueClassLowering, null) + else initialGetReceiver + irGetField(resultReceiver, field) + }, + { _, value: IrExpression -> + val initialGetReceiver = irGet(receiver) + val resultReceiver = + if (transformReceiver) initialGetReceiver.transform(this@JvmMultiFieldValueClassLowering, null) + else initialGetReceiver + irSetField(resultReceiver, field, value) + }, + ) + } + + fun IrBlockBuilder.flattenExpressionTo( + expression: IrExpression, variables: List, ExpressionSupplier>> + ) { + valueDeclarationsRemapper.implementationAgnostic(expression)?.virtualFields?.map { it.makeGetter(this, Unit) }?.let { + require(variables.size == it.size) + for ((variable, subExpression) in variables zip it) { + +variable.second(this, Unit, subExpression) + } + return + } + if (expression.type.isNullable() || !expression.type.isMultiFieldValueClassType()) { + require(variables.size == 1) + +variables.single().second(this, Unit, expression.transform(this@JvmMultiFieldValueClassLowering, null)) + return + } + val declarations = replacements.getDeclarations(expression.type.erasedUpperBound)!! + require(variables.size == declarations.leaves.size) + if (expression is IrConstructorCall) { + val constructor = expression.symbol.owner + if (constructor.isPrimary && constructor.constructedClass.isMultiFieldValueClass) { + val oldArguments = List(expression.valueArgumentsCount) { expression.getValueArgument(it) } + val root = declarations.loweringRepresentation + require(root.fields.size == oldArguments.size) { + "$constructor must have ${root.fields.size} arguments but got ${oldArguments.size}" + } + var curOffset = 0 + for ((treeField, argument) in root.fields zip oldArguments) { + val size = when (treeField.node) { + is InternalNode -> replacements.getDeclarations(treeField.node.irClass!!)!!.leaves.size + is MultiFieldValueClassTree.Leaf -> 1 + } + val subVariables = variables.slice(curOffset until (curOffset + size)).also { curOffset += size } + argument?.let { flattenExpressionTo(it, subVariables) } ?: List(size) { null } + } + +irCall(declarations.primaryConstructorImpl).apply { + variables.forEachIndexed { index, variable -> putValueArgument(index, variable.first(this@flattenExpressionTo, Unit)) } + } + return + } + } + val transformedExpression = expression.transform(this@JvmMultiFieldValueClassLowering, null) + if (transformedExpression is IrCall) { + val callee = transformedExpression.symbol.owner + if (callee == declarations.boxMethod) { + require(transformedExpression.valueArgumentsCount == declarations.fields.size) { + "Bad arguments number for box-method: ${transformedExpression.valueArgumentsCount}" + } + for ((variable, argument) in variables zip List(transformedExpression.valueArgumentsCount) { + transformedExpression.getValueArgument(it) + }) { + if (argument != null) { + +variable.second(this, Unit, argument) + } + } + return + } + } + (transformedExpression as? IrCall)?.let { makeLeavesGetters(it.symbol.owner) }?.let { geters -> + val receiver = irTemporary(transformedExpression.dispatchReceiver) + require(geters.size == variables.size) { "Number of getters must be equal to number of variables" } + for ((variable, getter) in variables zip geters) { + +variable.second(this, Unit, irCall(getter).apply { dispatchReceiver = irGet(receiver) }) + } + return + } + if (transformedExpression is IrContainerExpression && transformedExpression.statements.isNotEmpty()) { + val last = transformedExpression.statements.popLast() + if (last is IrExpression) { + transformedExpression.statements.forEach { +it } + flattenExpressionTo(last, variables) + return + } + } + val boxed = irTemporary(transformedExpression) + for ((variable, unboxMethod) in variables zip declarations.unboxMethods) { + +variable.second(this, Unit, irCall(unboxMethod).apply { dispatchReceiver = irGet(boxed) }) + } + } + + override fun visitStatementContainer(container: IrStatementContainer) { + super.visitStatementContainer(container) + deleteUselessBoxes(container) + } + + private fun deleteUselessBoxes(container: IrStatementContainer) { + val statements = container.statements + + // Removing box-impl's but not getters because they are auto-generated + fun IrExpression.isBoxCallStatement() = this is IrCall && valueDeclarationsRemapper.implementationAgnostic(this) != null && + List(valueArgumentsCount) { getValueArgument(it) }.all { it == null || it is IrGetField || it is IrGetValue } + + fun IrStatement.coercionToUnitArgument() = + (this as? IrTypeOperatorCall)?.takeIf { it.operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT }?.argument + + fun IrStatement.isBoxCallStatement(): Boolean { + val coercionToUnitArgument = coercionToUnitArgument() + if (coercionToUnitArgument?.isBoxCallStatement() == true || this is IrExpression && this.isBoxCallStatement()) { + return true + } + if (coercionToUnitArgument is IrStatementContainer && (coercionToUnitArgument.statements.lastOrNull() as? IrExpression)?.isBoxCallStatement() == true) { + coercionToUnitArgument.statements.popLast() + } + return false + } + + if (statements.isEmpty()) return + + val last = statements.popLast() + + statements.removeIf { it.isBoxCallStatement() } + for (statement in statements) { + if (statement is IrStatementContainer && statement.statements.lastOrNull()?.isBoxCallStatement() == true) { + statement.statements.removeLast() + } + } + statements.add(last) // we don't check delete it anyway + } + + private fun IrSimpleFunction.isDefaultGetter(field: IrField): Boolean = + ((body?.statements?.singleOrNull() as? IrReturn)?.value as? IrGetField)?.symbol?.owner == field +} diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt index 9a0453c82ed..760e784994f 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt @@ -8,6 +8,8 @@ package org.jetbrains.kotlin.backend.jvm.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.pop +import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.jvm.InlineClassAbi import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.MemoizedValueClassAbstractReplacements @@ -15,63 +17,28 @@ import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.irReturn import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* -import org.jetbrains.kotlin.ir.symbols.IrValueSymbol import org.jetbrains.kotlin.ir.transformStatement -import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.util.parentAsClass +import org.jetbrains.kotlin.ir.util.patchDeclarationParents +import org.jetbrains.kotlin.ir.util.transformFlat import org.jetbrains.kotlin.utils.addToStdlib.safeAs internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() { abstract val replacements: MemoizedValueClassAbstractReplacements - protected val valueMap = mutableMapOf() - - private fun addBindingsFor(original: IrFunction, replacement: IrFunction) { - for ((param, newParam) in original.explicitParameters.zip(replacement.explicitParameters)) { - valueMap[param.symbol] = newParam - } - } - final override fun lower(irFile: IrFile) { irFile.transformChildrenVoid() } abstract fun IrClass.isSpecificLoweringLogicApplicable(): Boolean - abstract fun IrFunction.isSpecificFieldGetter(): Boolean + abstract fun IrFunction.isFieldGetterToRemove(): Boolean - final override fun visitClassNew(declaration: IrClass): IrStatement { - // The arguments to the primary constructor are in scope in the initializers of IrFields. - declaration.primaryConstructor?.let { - replacements.getReplacementFunction(it)?.let { replacement -> addBindingsFor(it, replacement) } - } + abstract override fun visitClassNew(declaration: IrClass): IrStatement - declaration.transformDeclarationsFlat { memberDeclaration -> - if (memberDeclaration is IrFunction) { - withinScope(memberDeclaration) { - transformFunctionFlat(memberDeclaration) - } - } else { - memberDeclaration.accept(this, null) - null - } - } - - if (declaration.isSpecificLoweringLogicApplicable()) { - val irConstructor = declaration.primaryConstructor!! - // The field getter is used by reflection and cannot be removed here unless it is internal. - declaration.declarations.removeIf { - it == irConstructor || (it is IrFunction && it.isSpecificFieldGetter() && !it.visibility.isPublicAPI) - } - buildPrimaryValueClassConstructor(declaration, irConstructor) - buildBoxFunction(declaration) - buildUnboxFunctions(declaration) - buildSpecializedEqualsMethod(declaration) - addJvmInlineAnnotation(declaration) - } - - return declaration - } + abstract fun handleSpecificNewClass(declaration: IrClass) protected fun transformFunctionFlat(function: IrFunction): List? { if (function is IrConstructor && function.isPrimary && function.constructedClass.isSpecificLoweringLogicApplicable()) { @@ -79,7 +46,15 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon } val replacement = replacements.getReplacementFunction(function) + if (replacement == null) { + if (function is IrConstructor) { + val constructorReplacement = replacements.getReplacementRegularClassConstructor(function) + if (constructorReplacement != null) { + addBindingsFor(function, constructorReplacement) + return transformFlattenedConstructor(function, constructorReplacement) + } + } function.transformChildrenVoid() // Non-mangled functions can override mangled functions under some conditions, e.g., a function // `fun f(): Nothing` can override a function `fun f(): UInt`. The former is not mangled, while @@ -106,30 +81,48 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon addBindingsFor(function, replacement) return when (function) { is IrSimpleFunction -> transformSimpleFunctionFlat(function, replacement) - is IrConstructor -> transformConstructorFlat(function, replacement) + is IrConstructor -> transformSecondaryConstructorFlat(function, replacement) else -> throw IllegalStateException() } } + private fun transformFlattenedConstructor(function: IrConstructor, replacement: IrConstructor): List? { + replacement.valueParameters.forEach { + it.transformChildrenVoid() + it.defaultValue?.patchDeclarationParents(replacement) + } + allScopes.push(createScope(function)) + replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement) + allScopes.pop() + return listOf(replacement) + } + private fun IrFunction.hashSuffix(): String? = InlineClassAbi.hashSuffix( this, context.state.functionsWithInlineClassReturnTypesMangled, context.state.useOldManglingSchemeForFunctionsWithInlineClassesInSignatures ) - protected abstract fun transformConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List + protected abstract fun transformSecondaryConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List - protected abstract fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List + private fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List { + replacement.valueParameters.forEach { + it.transformChildrenVoid() + it.defaultValue?.patchDeclarationParents(replacement) + } + allScopes.push(createScope(function)) + replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement) + allScopes.pop() + replacement.copyAttributes(function) - protected abstract fun buildPrimaryValueClassConstructor(valueClass: IrClass, irConstructor: IrConstructor) + // Don't create a wrapper for functions which are only used in an unboxed context + if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null) + return listOf(replacement) - protected abstract fun buildBoxFunction(valueClass: IrClass) + val bridgeFunction = createBridgeFunction(function, replacement) - protected abstract fun buildUnboxFunctions(valueClass: IrClass) - - protected abstract fun buildSpecializedEqualsMethod(valueClass: IrClass) // todo hashCode - - protected abstract fun addJvmInlineAnnotation(valueClass: IrClass) + return listOfNotNull(replacement, bridgeFunction) + } final override fun visitReturn(expression: IrReturn): IrExpression { expression.returnTargetSymbol.owner.safeAs()?.let { target -> @@ -146,7 +139,7 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon return super.visitReturn(expression) } - private fun visitStatementContainer(container: IrStatementContainer) { + protected open fun visitStatementContainer(container: IrStatementContainer) { container.statements.transformFlat { statement -> if (statement is IrFunction) transformFunctionFlat(statement) @@ -172,4 +165,11 @@ internal abstract class JvmValueClassAbstractLowering(val context: JvmBackendCon else super.visitAnonymousInitializerNew(declaration) + protected abstract fun addBindingsFor(original: IrFunction, replacement: IrFunction) + protected abstract fun createBridgeFunction(function: IrSimpleFunction, replacement: IrSimpleFunction): IrSimpleFunction? + + protected fun typedArgumentList(function: IrFunction, expression: IrMemberAccessExpression<*>) = listOfNotNull( + function.dispatchReceiverParameter?.let { it to expression.dispatchReceiver }, + function.extensionReceiverParameter?.let { it to expression.extensionReceiver } + ) + function.valueParameters.map { it to expression.getValueArgument(it.index) } } \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt index 3f4c48ab267..8e0484fd8e0 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt @@ -60,14 +60,14 @@ object InlineClassAbi { fun mangledNameFor(irFunction: IrFunction, mangleReturnTypes: Boolean, useOldMangleRules: Boolean): Name { if (irFunction is IrConstructor) { // Note that we might drop this convention and use standard mangling for constructors too, see KT-37186. - assert(irFunction.constructedClass.isSingleFieldValueClass) { + assert(irFunction.constructedClass.isValue) { "Should not mangle names of non-inline class constructors: ${irFunction.render()}" } return Name.identifier("constructor-impl") } val suffix = hashSuffix(irFunction, mangleReturnTypes, useOldMangleRules) - if (suffix == null && ((irFunction.parent as? IrClass)?.isSingleFieldValueClass != true || irFunction.origin == IrDeclarationOrigin.IR_BUILTINS_STUB)) { + if (suffix == null && ((irFunction.parent as? IrClass)?.isValue != true || irFunction.origin == IrDeclarationOrigin.IR_BUILTINS_STUB)) { return irFunction.name } @@ -127,16 +127,16 @@ object InlineClassAbi { val IrType.requiresMangling: Boolean get() { val irClass = erasedUpperBound - return irClass.isSingleFieldValueClass && irClass.fqNameWhenAvailable != StandardNames.RESULT_FQ_NAME + return irClass.isValue && irClass.fqNameWhenAvailable != StandardNames.RESULT_FQ_NAME } val IrFunction.fullValueParameterList: List get() = listOfNotNull(extensionReceiverParameter) + valueParameters val IrFunction.hasMangledParameters: Boolean - get() = dispatchReceiverParameter != null && parentAsClass.isSingleFieldValueClass || + get() = dispatchReceiverParameter != null && parentAsClass.isValue || fullValueParameterList.any { it.type.requiresMangling } || - (this is IrConstructor && constructedClass.isSingleFieldValueClass) + (this is IrConstructor && constructedClass.isValue) val IrFunction.hasMangledReturnType: Boolean get() = returnType.isInlineClassType() && parentClassOrNull?.isFileClass != true @@ -148,7 +148,7 @@ val IrFunction.isInlineClassFieldGetter: Boolean get() = (parent as? IrClass)?.isSingleFieldValueClass == true && this is IrSimpleFunction && extensionReceiverParameter == null && correspondingPropertySymbol?.let { it.owner.getter == this && it.owner.name == parentAsClass.inlineClassFieldName } == true -val IrFunction.isMultiFieldValueClassFieldGetter: Boolean +val IrFunction.isMultiFieldValueClassOriginalFieldGetter: Boolean get() = (parent as? IrClass)?.isMultiFieldValueClass == true && this is IrSimpleFunction && extensionReceiverParameter == null && correspondingPropertySymbol?.let { val multiFieldValueClassRepresentation = parentAsClass.multiFieldValueClassRepresentation diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index fa58f3b88f9..26ced1a787c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -31,6 +31,7 @@ import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrTypeSystemContext +import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl import org.jetbrains.kotlin.ir.types.defaultType import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.name.FqName @@ -143,7 +144,8 @@ class JvmBackendContext( val inlineClassReplacements = MemoizedInlineClassReplacements(state.functionsWithInlineClassReturnTypesMangled, irFactory, this) - val multiFieldValueClassReplacements = MemoizedMultiFieldValueClassReplacements(irFactory, this) + val multiFieldValueClassReplacements = + MemoizedMultiFieldValueClassReplacements(irFactory, this, IrTypeSystemContextImpl(irBuiltIns)) val continuationClassesVarsCountByType: MutableMap> = hashMapOf() @@ -197,6 +199,20 @@ class JvmBackendContext( multifileFacade.setValue(newPartClasses.toMutableList()) } + for ((staticReplacement, original) in multiFieldValueClassReplacements.originalFunctionForStaticReplacement) { + if (staticReplacement !is IrSimpleFunction) continue + val newOriginal = functionSymbolMap[original.symbol]?.owner ?: continue + val newStaticReplacement = multiFieldValueClassReplacements.getReplacementFunction(newOriginal) ?: continue + functionSymbolMap[staticReplacement.symbol] = newStaticReplacement.symbol + } + + for ((methodReplacement, original) in multiFieldValueClassReplacements.originalFunctionForMethodReplacement) { + if (methodReplacement !is IrSimpleFunction) continue + val newOriginal = functionSymbolMap[original.symbol]?.owner ?: continue + val newMethodReplacement = multiFieldValueClassReplacements.getReplacementFunction(newOriginal) ?: continue + functionSymbolMap[methodReplacement.symbol] = newMethodReplacement.symbol + } + for ((staticReplacement, original) in inlineClassReplacements.originalFunctionForStaticReplacement) { if (staticReplacement !is IrSimpleFunction) continue val newOriginal = functionSymbolMap[original.symbol]?.owner ?: continue diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt index fa74ad9706a..42a27c6a9ed 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt @@ -29,9 +29,14 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin { object ENUM_MAPPINGS_FOR_WHEN : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_WHEN", isSynthetic = true) object ENUM_MAPPINGS_FOR_ENTRIES : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_ENTRIES", isSynthetic = true) object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true) + object SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER : + IrDeclarationOriginImpl("SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER", isSynthetic = true) object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD") + object MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD") object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT") + object STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT") object STATIC_INLINE_CLASS_CONSTRUCTOR : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_CONSTRUCTOR") + object STATIC_MULTI_FIELD_VALUE_CLASS_CONSTRUCTOR : IrDeclarationOriginImpl("STATIC_MULTI_FIELD_VALUE_CLASS_CONSTRUCTOR") object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true) object GENERATED_EXTENDED_MAIN : IrDeclarationOriginImpl("GENERATED_EXTENDED_MAIN", isSynthetic = true) object SUSPEND_IMPL_STATIC_FUNCTION : IrDeclarationOriginImpl("SUSPEND_IMPL_STATIC_FUNCTION", isSynthetic = true) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt index 1060c6c1cac..88b71bc1f40 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt @@ -5,6 +5,10 @@ package org.jetbrains.kotlin.backend.jvm +import org.jetbrains.kotlin.backend.common.ir.copyTo +import org.jetbrains.kotlin.backend.common.ir.copyTypeParameters +import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom +import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter import org.jetbrains.kotlin.backend.jvm.ir.* import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.descriptors.DescriptorVisibilities @@ -12,18 +16,17 @@ import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.buildFun -import org.jetbrains.kotlin.ir.builders.declarations.buildProperty import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl -import org.jetbrains.kotlin.ir.types.isInt import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.kotlin.utils.alwaysNull import java.util.concurrent.ConcurrentHashMap /** @@ -35,9 +38,8 @@ class MemoizedInlineClassReplacements( context: JvmBackendContext ) : MemoizedValueClassAbstractReplacements(irFactory, context) { private val storageManager = LockBasedStorageManager("inline-class-replacements") - private val propertyMap = ConcurrentHashMap() - override val originalFunctionForStaticReplacement: MutableMap = ConcurrentHashMap() + val originalFunctionForStaticReplacement: MutableMap = ConcurrentHashMap() internal val originalFunctionForMethodReplacement: MutableMap = ConcurrentHashMap() /** @@ -49,7 +51,9 @@ class MemoizedInlineClassReplacements( // Don't mangle anonymous or synthetic functions, except for generated SAM wrapper methods (it.isLocal && it is IrSimpleFunction && it.overriddenSymbols.isEmpty()) || (it.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR && it.visibility == DescriptorVisibilities.LOCAL) || - it.isStaticInlineClassReplacement || + it.isStaticValueClassReplacement || + it.origin == JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD || + it.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER || it.origin.isSynthetic && it.origin != IrDeclarationOrigin.SYNTHETIC_GENERATED_SAM_IMPLEMENTATION -> null @@ -64,7 +68,7 @@ class MemoizedInlineClassReplacements( when { it.isRemoveAtSpecialBuiltinStub() -> null - it.isInlineClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod() || + it.isValueClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod() || it.origin == IrDeclarationOrigin.IR_BUILTINS_STUB -> createMethodReplacement(it) else -> @@ -80,29 +84,6 @@ class MemoizedInlineClassReplacements( } } - private fun IrFunction.isRemoveAtSpecialBuiltinStub() = - origin == IrDeclarationOrigin.IR_BUILTINS_STUB && - name.asString() == "remove" && - valueParameters.size == 1 && - valueParameters[0].type.isInt() - - private fun IrFunction.isInlineClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod(): Boolean { - if (this !is IrSimpleFunction) return false - if (!this.isFakeOverride) return false - val parentClass = parentClassOrNull ?: return false - if (!parentClass.isSingleFieldValueClass) return false - - val overridden = resolveFakeOverride() ?: return false - if (!overridden.parentAsClass.isJvmInterface) return false - if (overridden.modality == Modality.ABSTRACT) return false - - // We have a non-abstract interface member. - // It is a JVM default interface method if one of the following conditions are true: - // - it is a Java method, - // - it is a Kotlin function compiled to JVM default interface method. - return overridden.isFromJava() || overridden.isCompiledToJvmDefault(context.state.jvmDefaultMode) - } - /** * Get the box function for an inline class. Concretely, this is a synthetic * static function named "box-impl" which takes an unboxed value and returns @@ -148,7 +129,7 @@ class MemoizedInlineClassReplacements( return specializedEqualsCache.computeIfAbsent(irClass) { irFactory.buildFun { name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME - // TODO: Revisit this once we allow user defined equals methods in inline classes. + // TODO: Revisit this once we allow user defined equals methods in inline/multi-field value classes. origin = JvmLoweredDeclarationOrigin.INLINE_CLASS_GENERATED_IMPL_METHOD returnType = irBuiltIns.booleanType }.apply { @@ -168,7 +149,7 @@ class MemoizedInlineClassReplacements( } } - private fun createMethodReplacement(function: IrFunction): IrSimpleFunction = + override fun createMethodReplacement(function: IrFunction): IrSimpleFunction = buildReplacement(function, function.origin) { originalFunctionForMethodReplacement[this] = function dispatchReceiverParameter = function.dispatchReceiverParameter?.copyTo(this, index = -1) @@ -186,7 +167,7 @@ class MemoizedInlineClassReplacements( } } - private fun createStaticReplacement(function: IrFunction): IrSimpleFunction = + override fun createStaticReplacement(function: IrFunction): IrSimpleFunction = buildReplacement(function, JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_REPLACEMENT, noFakeOverride = true) { originalFunctionForStaticReplacement[this] = function @@ -246,8 +227,7 @@ class MemoizedInlineClassReplacements( noFakeOverride: Boolean, useOldManglingScheme: Boolean, body: IrFunction.() -> Unit, - ): IrSimpleFunction = irFactory.buildFun { - updateFrom(function) + ): IrSimpleFunction = commonBuildReplacementInner(function, noFakeOverride, body) { if (function is IrConstructor) { // The [updateFrom] call will set the modality to FINAL for constructors, while the JVM backend would use OPEN here. modality = Modality.OPEN @@ -260,53 +240,11 @@ class MemoizedInlineClassReplacements( else -> replacementOrigin } - if (noFakeOverride) { - isFakeOverride = false - } name = InlineClassAbi.mangledNameFor(function, mangleReturnTypes, useOldManglingScheme) - returnType = function.returnType - }.apply { - parent = function.parent - annotations = function.annotations - copyTypeParameters(function.allTypeParameters) - if (function.metadata != null) { - metadata = function.metadata - function.metadata = null - } - copyAttributes(function as? IrAttributeContainer) - - if (function is IrSimpleFunction) { - val propertySymbol = function.correspondingPropertySymbol - if (propertySymbol != null) { - val property = propertyMap.getOrPut(propertySymbol) { - irFactory.buildProperty { - name = propertySymbol.owner.name - updateFrom(propertySymbol.owner) - }.apply { - parent = propertySymbol.owner.parent - copyAttributes(propertySymbol.owner) - annotations = propertySymbol.owner.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 unlowered. - backingField = context.cachedDeclarations.getStaticBackingField(propertySymbol.owner) - ?: propertySymbol.owner.backingField - } - } - correspondingPropertySymbol = property.symbol - when (function) { - propertySymbol.owner.getter -> property.getter = this - propertySymbol.owner.setter -> property.setter = this - else -> error("Orphaned property getter/setter: ${function.render()}") - } - } - - overriddenSymbols = replaceOverriddenSymbols(function) - } - - body() } + override val getReplacementRegularClassConstructor: (IrConstructor) -> IrConstructor? = alwaysNull() + override val replaceOverriddenSymbols: (IrSimpleFunction) -> List = storageManager.createMemoizedFunction { irSimpleFunction -> irSimpleFunction.overriddenSymbols.map { 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 bac2f39420b..4d34bbf7d53 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 @@ -5,11 +5,24 @@ package org.jetbrains.kotlin.backend.jvm -import org.jetbrains.kotlin.ir.declarations.IrFactory -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.backend.common.ir.copyTo +import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom +import org.jetbrains.kotlin.backend.jvm.ir.extensionReceiverName +import org.jetbrains.kotlin.backend.jvm.ir.isMultiFieldValueClassType +import org.jetbrains.kotlin.backend.jvm.ir.isStaticValueClassReplacement +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrExpressionBody import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.IrTypeSystemContext +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.isNullable +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.storage.LockBasedStorageManager +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.util.concurrent.ConcurrentHashMap /** @@ -17,21 +30,239 @@ import java.util.concurrent.ConcurrentHashMap */ class MemoizedMultiFieldValueClassReplacements( irFactory: IrFactory, - context: JvmBackendContext + context: JvmBackendContext, + private val typeSystemContext: IrTypeSystemContext ) : MemoizedValueClassAbstractReplacements(irFactory, context) { private val storageManager = LockBasedStorageManager("multi-field-value-class-replacements") - override val originalFunctionForStaticReplacement: MutableMap = ConcurrentHashMap() + internal val originalFunctionForStaticReplacement: MutableMap = ConcurrentHashMap() internal val originalFunctionForMethodReplacement: MutableMap = ConcurrentHashMap() + internal val originalConstructorForConstructorReplacement: MutableMap = ConcurrentHashMap() + + val getDeclarations: (IrClass) -> MultiFieldValueClassSpecificDeclarations? = + storageManager.createMemoizedFunctionWithNullableValues { + if (it.isMultiFieldValueClass) + MultiFieldValueClassSpecificDeclarations(it, typeSystemContext, irFactory, context, this) + else + null + } + + private val oldFields: MutableMap> = + ConcurrentHashMap>().withDefault { it.fields.toList() } + + fun setOldFields(irClass: IrClass, fields: List) { + oldFields[irClass] = fields + } + + fun getOldFields(irClass: IrClass): List = oldFields.getOrPut(irClass) { irClass.fields.toList() } + + class ValueParameterTemplate( + val name: String, + val type: IrType, + val origin: IrDeclarationOrigin?, + val defaultValue: IrExpressionBody?, + val original: IrValueParameter, + ) { + fun toParameter(irFunction: IrFunction, index: Int): IrValueParameter = original.copyTo( + irFunction = irFunction, + index = index, + name = Name.guessByFirstCharacter(name), + origin = origin ?: original.origin, + defaultValue = null, + type = type, + ).also { + // Assuming that constructors and non-override functions are always replaced with the unboxed + // equivalent, deep-copying the value here is unnecessary. + it.defaultValue = defaultValue?.patchDeclarationParents(irFunction) + } + } + + private fun IrFunction.makeValueParametersFromTemplate(newFlattenedParameters: List>) = + newFlattenedParameters.flatten().mapIndexed { index: Int, template -> template.toParameter(this, index) } + + private fun List.grouped( + originWhenFlattenedAndNotSpecified: IrDeclarationOrigin? = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_PARAMETER + ): List> = map { parameter -> + val declaration = parameter.type.takeIf { !it.isNullable() }?.getClass()?.let { getDeclarations(it) } + ?: return@map listOf(parameter) + require(!parameter.original.hasDefaultValue()) { "Default parameters values are not supported for multi-field value classes" } + declaration.leaves.map { leaf -> + ValueParameterTemplate( + name = "${parameter.name}$${declaration.nodeFullNames[leaf]!!}", + type = leaf.type, + origin = parameter.origin ?: originWhenFlattenedAndNotSpecified, + defaultValue = null, + original = parameter.original, + ) + } + } + + private fun buildReplacement( + function: IrFunction, + replacementOrigin: IrDeclarationOrigin, + noFakeOverride: Boolean = false, + body: IrFunction.() -> Unit, + ): IrSimpleFunction = commonBuildReplacementInner(function, noFakeOverride, body) { + origin = when { + function.origin == IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER -> + JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD + function is IrConstructor && function.constructedClass.isMultiFieldValueClass -> + JvmLoweredDeclarationOrigin.STATIC_MULTI_FIELD_VALUE_CLASS_CONSTRUCTOR + else -> replacementOrigin + } + name = InlineClassAbi.mangledNameFor(function, mangleReturnTypes = false, useOldMangleRules = false) + } + + private fun makeGroupedValueParametersFrom( + function: IrFunction, includeDispatcherReceiver: Boolean + ): List> { + val newFlattenedParameters = mutableListOf>() + if (function.dispatchReceiverParameter != null && includeDispatcherReceiver) { + val template = ValueParameterTemplate( + name = "\$dispatchReceiver", + type = function.parentAsClass.defaultType, + origin = IrDeclarationOrigin.MOVED_DISPATCH_RECEIVER, + defaultValue = null, + original = function.parentAsClass.thisReceiver!!, + ) + newFlattenedParameters.addAll(listOf(template).grouped()) + } + val contextReceivers = function.valueParameters.take(function.contextReceiverParametersCount) + .mapIndexed { index: Int, valueParameter: IrValueParameter -> + ValueParameterTemplate( + name = "contextReceiver$index", + origin = IrDeclarationOrigin.MOVED_CONTEXT_RECEIVER, + type = valueParameter.type, + defaultValue = valueParameter.defaultValue, + original = valueParameter, + ) + } + .grouped() + newFlattenedParameters.addAll(contextReceivers) + function.extensionReceiverParameter?.let { + val template = ValueParameterTemplate( + name = Name.identifier(function.extensionReceiverName(context.state)).asString(), + origin = IrDeclarationOrigin.MOVED_EXTENSION_RECEIVER, + type = it.type, + defaultValue = it.defaultValue, + original = it, + ) + newFlattenedParameters.addAll(listOf(template).grouped()) + } + newFlattenedParameters += function.valueParameters.drop(function.contextReceiverParametersCount).map { + ValueParameterTemplate( + name = it.name.asString(), + type = it.type, + defaultValue = it.defaultValue, + original = it, + origin = null, + ) + }.grouped() + return newFlattenedParameters + } + + val bindingParameterTemplateStructure: MutableMap>> = ConcurrentHashMap() + + override fun createStaticReplacement(function: IrFunction): IrSimpleFunction = + buildReplacement(function, JvmLoweredDeclarationOrigin.STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT, noFakeOverride = true) { + originalFunctionForStaticReplacement[this] = function + val newFlattenedParameters = makeGroupedValueParametersFrom(function, includeDispatcherReceiver = true) + bindingParameterTemplateStructure[function] = newFlattenedParameters + valueParameters = makeValueParametersFromTemplate(newFlattenedParameters) + } + + override fun createMethodReplacement(function: IrFunction): IrSimpleFunction = buildReplacement(function, function.origin) { + originalFunctionForMethodReplacement[this] = function + dispatchReceiverParameter = function.dispatchReceiverParameter?.copyTo(this, index = -1) + val newFlattenedParameters = makeGroupedValueParametersFrom(function, includeDispatcherReceiver = false) + val receiver = dispatchReceiverParameter + val receiverTemplate = if (receiver != null) ValueParameterTemplate( + name = receiver.name.asString(), + type = receiver.type, + origin = receiver.origin, + defaultValue = receiver.defaultValue, + original = receiver, + ) else null + bindingParameterTemplateStructure[function] = + if (receiverTemplate != null) listOf(listOf(receiverTemplate)) + newFlattenedParameters else newFlattenedParameters + valueParameters = makeValueParametersFromTemplate(newFlattenedParameters) + } + + private fun createConstructorReplacement(@Suppress("UNUSED_PARAMETER") constructor: IrConstructor): IrConstructor { + val newFlattenedParameters = makeGroupedValueParametersFrom(constructor, includeDispatcherReceiver = false) + bindingParameterTemplateStructure[constructor] = newFlattenedParameters + return irFactory.buildConstructor { + updateFrom(constructor) + returnType = constructor.returnType + }.apply { + copyTypeParametersFrom(constructor) + valueParameters = makeValueParametersFromTemplate(newFlattenedParameters) + annotations = constructor.annotations + parent = constructor.parent + originalConstructorForConstructorReplacement[this] = constructor + } + } + + private fun createGetterReplacement(function: IrFunction): IrSimpleFunction { + val declarations = getDeclarations(function.parentAsClass)!! + val name = (function as IrSimpleFunction).correspondingPropertySymbol!!.owner.name + val node = declarations.loweringRepresentation[name]!!.node + val newGetter = declarations.properties[node]!!.getter!! + originalFunctionForMethodReplacement[newGetter] = function + val receiver = function.dispatchReceiverParameter!! + val receiverTemplate = ValueParameterTemplate( + name = receiver.name.asString(), + type = receiver.type, + origin = receiver.origin, + defaultValue = receiver.defaultValue, + original = receiver + ) + bindingParameterTemplateStructure[function] = listOf(listOf(receiverTemplate)) + return newGetter + } /** * Get a replacement for a function or a constructor. */ - override val getReplacementFunction: (IrFunction) -> IrSimpleFunction? = storageManager.createMemoizedFunctionWithNullableValues { - TODO() - } + override val getReplacementFunction: (IrFunction) -> IrSimpleFunction? = + storageManager.createMemoizedFunctionWithNullableValues { function -> + when { + (function.isLocal && function is IrSimpleFunction && function.overriddenSymbols.isEmpty()) || + (function.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR && function.visibility == DescriptorVisibilities.LOCAL) || + 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 -> null + function.isMultiFieldValueClassOriginalFieldGetter -> createGetterReplacement(function) + function.parent.safeAs()?.isMultiFieldValueClass == true -> when { + function.isRemoveAtSpecialBuiltinStub() -> + null + function.isValueClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod() || + function.origin == IrDeclarationOrigin.IR_BUILTINS_STUB -> + createMethodReplacement(function) + else -> + createStaticReplacement(function) + } + function is IrSimpleFunction && !function.isFromJava() && + function.fullValueParameterList.any { it.type.isMultiFieldValueClassType() && !it.type.isNullable() } -> + createMethodReplacement(function) + else -> null + } + } + + override val getReplacementRegularClassConstructor: (IrConstructor) -> IrConstructor? = + storageManager.createMemoizedFunctionWithNullableValues { constructor -> + when { + constructor.constructedClass.isMultiFieldValueClass -> null + constructor.isFromJava() -> null + constructor.fullValueParameterList.any { !it.type.isNullable() && it.type.isMultiFieldValueClassType() } -> + createConstructorReplacement(constructor) + else -> null + } + } override val replaceOverriddenSymbols: (IrSimpleFunction) -> List = storageManager.createMemoizedFunction { TODO() } } + 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 e94c6cd2088..9590fbe9656 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 @@ -5,18 +5,112 @@ package org.jetbrains.kotlin.backend.jvm -import org.jetbrains.kotlin.ir.declarations.IrFactory -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.backend.common.ir.copyTypeParameters +import org.jetbrains.kotlin.backend.jvm.ir.isCompiledToJvmDefault +import org.jetbrains.kotlin.backend.jvm.ir.isJvmInterface +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.builders.declarations.IrFunctionBuilder +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.builders.declarations.buildProperty +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.symbols.IrPropertySymbol +import org.jetbrains.kotlin.ir.types.isInt +import org.jetbrains.kotlin.ir.util.* +import java.util.concurrent.ConcurrentHashMap abstract class MemoizedValueClassAbstractReplacements(protected val irFactory: IrFactory, protected val context: JvmBackendContext) { + protected val propertyMap = ConcurrentHashMap() + /** * Get a replacement for a function or a constructor. */ abstract val getReplacementFunction: (IrFunction) -> IrSimpleFunction? - abstract val originalFunctionForStaticReplacement: MutableMap + protected fun IrFunction.isRemoveAtSpecialBuiltinStub() = + origin == IrDeclarationOrigin.IR_BUILTINS_STUB && + name.asString() == "remove" && + valueParameters.size == 1 && + valueParameters[0].type.isInt() + + protected fun IrFunction.isValueClassMemberFakeOverriddenFromJvmDefaultInterfaceMethod(): Boolean { + if (this !is IrSimpleFunction) return false + if (!this.isFakeOverride) return false + val parentClass = parentClassOrNull ?: return false + require(parentClass.isValue) + + val overridden = resolveFakeOverride() ?: return false + if (!overridden.parentAsClass.isJvmInterface) return false + if (overridden.modality == Modality.ABSTRACT) return false + + // We have a non-abstract interface member. + // It is a JVM default interface method if one of the following conditions are true: + // - it is a Java method, + // - it is a Kotlin function compiled to JVM default interface method. + return overridden.isFromJava() || overridden.isCompiledToJvmDefault(context.state.jvmDefaultMode) + } + + protected abstract fun createStaticReplacement(function: IrFunction): IrSimpleFunction + protected abstract fun createMethodReplacement(function: IrFunction): IrSimpleFunction + + protected fun commonBuildReplacementInner( + function: IrFunction, + noFakeOverride: Boolean, + body: IrFunction.() -> Unit, + builderBody: IrFunctionBuilder.() -> Unit, + ): IrSimpleFunction = irFactory.buildFun { + updateFrom(function) + builderBody() + if (noFakeOverride) { + isFakeOverride = false + } + returnType = function.returnType + }.apply { + parent = function.parent + annotations = function.annotations + copyTypeParameters(function.allTypeParameters) + if (function.metadata != null) { + metadata = function.metadata + function.metadata = null + } + copyAttributes(function as? IrAttributeContainer) + + if (function is IrSimpleFunction) { + val propertySymbol = function.correspondingPropertySymbol + if (propertySymbol != null) { + val property = propertyMap.getOrPut(propertySymbol) { + irFactory.buildProperty { + name = propertySymbol.owner.name + updateFrom(propertySymbol.owner) + }.apply { + parent = propertySymbol.owner.parent + copyAttributes(propertySymbol.owner) + annotations = propertySymbol.owner.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 unlowered. + backingField = context.cachedDeclarations.getStaticBackingField(propertySymbol.owner) + ?: propertySymbol.owner.backingField + } + } + correspondingPropertySymbol = property.symbol + when (function) { + propertySymbol.owner.getter -> property.getter = this + propertySymbol.owner.setter -> property.setter = this + else -> error("Orphaned property getter/setter: ${function.render()}") + } + } + + overriddenSymbols = function.overriddenSymbols.map { + getReplacementFunction(it.owner)?.symbol ?: it + } + } + + body() + } abstract val replaceOverriddenSymbols: (IrSimpleFunction) -> List + + abstract val getReplacementRegularClassConstructor: (IrConstructor) -> IrConstructor? } \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MultiFieldValueClassSpecificDeclarations.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MultiFieldValueClassSpecificDeclarations.kt new file mode 100644 index 00000000000..de691750445 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MultiFieldValueClassSpecificDeclarations.kt @@ -0,0 +1,490 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.jvm + +import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom +import org.jetbrains.kotlin.backend.common.ir.createDispatchReceiverParameter +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irBlockBody +import org.jetbrains.kotlin.backend.common.pop +import org.jetbrains.kotlin.backend.jvm.MultiFieldValueClassTree.InternalNode +import org.jetbrains.kotlin.backend.jvm.MultiFieldValueClassTree.Leaf +import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +import org.jetbrains.kotlin.backend.jvm.ir.isMultiFieldValueClassType +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.DescriptorVisibility +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.MultiFieldValueClassRepresentation +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.builders.declarations.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrConstructorCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetField +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.constructedClass +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.ir.util.primaryConstructor +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.InlineClassDescriptorResolver + +sealed class MultiFieldValueClassTree { + abstract val type: IrType + val irClass: IrClass? + get() = type.getClass() + + companion object { + @JvmStatic + fun create( + type: IrType, + defaultInternalNodeValue: TI, + defaultLeafValue: TL, + replacements: MemoizedMultiFieldValueClassReplacements + ) = + if (!type.isNullable() && type.isMultiFieldValueClassType()) + InternalNode(type, defaultInternalNodeValue, DescriptorVisibilities.PUBLIC, defaultLeafValue, replacements) + else + Leaf(type, defaultLeafValue) + + @JvmStatic + fun create(type: IrType, replacements: MemoizedMultiFieldValueClassReplacements) = create(type, Unit, Unit, replacements) + } + + data class Leaf(override val type: IrType, val leafValue: TL) : MultiFieldValueClassTree() { + init { + require(type.isNullable() || !type.isMultiFieldValueClassType()) + } + + fun map(transformLeaf: Leaf.(TL) -> RL) = Leaf(type, transformLeaf(leafValue)) + } + + data class InternalNode internal constructor( + override val type: IrType, val internalNodeValue: TI, val fields: List> + ) : MultiFieldValueClassTree() { + constructor( + type: IrType, defaultInternalNodeValue: TI, visibility: DescriptorVisibility, defaultLeafValue: TL, + replacements: MemoizedMultiFieldValueClassReplacements + ) : this(type, defaultInternalNodeValue, 0.let { + val valueClassRepresentation = type.erasedUpperBound.valueClassRepresentation as MultiFieldValueClassRepresentation + val primaryConstructor = type.erasedUpperBound.primaryConstructor!! + val fieldsByName = replacements.getOldFields(primaryConstructor.constructedClass).associateBy { it.name } + valueClassRepresentation.underlyingPropertyNamesToTypes.map { (name, type) -> + val field = fieldsByName[name]!! + val innerVisibility = field.correspondingPropertySymbol!!.owner.visibility + val comparison = visibility.compareTo(innerVisibility) + ?: error("Expected comparable visibilities but got $visibility and $innerVisibility") + val newVisibility = if (comparison < 0) visibility else innerVisibility + TreeField(name, type, defaultInternalNodeValue, newVisibility, field.annotations, defaultLeafValue, replacements) + } + }) + + data class TreeField( + val name: Name, val type: IrType, val visibility: DescriptorVisibility, val annotations: List, + val node: MultiFieldValueClassTree + ) { + constructor( + name: Name, + type: IrType, + internalNodeValue: TI, + visibility: DescriptorVisibility, + annotations: List, + defaultLeafValue: TL, + replacements: MemoizedMultiFieldValueClassReplacements + ) : this(name, type, visibility, annotations, create(type, internalNodeValue, defaultLeafValue, replacements)) + + fun map( + transformInternalNode: InternalNode.(TI) -> RI, + transformLeaf: Leaf.(TL) -> RL + ): TreeField = TreeField(name, type, visibility, annotations, node.map(transformInternalNode, transformLeaf)) + } + + // todo store real fields, cannot restore them + // todo or store fields not pairs + + init { + require(!type.isNullable() && type.isMultiFieldValueClassType()) + } + + private val fieldByName = fields.associateBy { it.name } + + operator fun get(name: Name) = fieldByName[name] + + override fun map( + transformInternalNode: InternalNode.(TI) -> RI, + transformLeaf: Leaf.(TL) -> RL + ): InternalNode = InternalNode( + type = type, + internalNodeValue = transformInternalNode(internalNodeValue), + fields = fields.map { it.map(transformInternalNode, transformLeaf) }, + ) + + } + + interface Visitor { + fun visitLeaf(treeNode: Leaf): T + fun visitInternalNode(treeNode: InternalNode): T + } + + fun visit(visitor: Visitor) = when (this) { + is InternalNode -> visitor.visitInternalNode(this) + is Leaf -> visitor.visitLeaf(this) + } + + open fun map( + transformInternalNode: InternalNode.(TI) -> RI, + transformLeaf: Leaf.(TL) -> RL + ): MultiFieldValueClassTree = when (this) { + is InternalNode -> map(transformInternalNode, transformLeaf) + is Leaf -> map(transformLeaf) + } +} + +class MultiFieldValueClassSpecificDeclarations( + val valueClass: IrClass, + private val typeSystemContext: IrTypeSystemContext, + private val irFactory: IrFactory, + private val context: JvmBackendContext, + private val replacements: MemoizedMultiFieldValueClassReplacements, +) { + init { + require(valueClass.isMultiFieldValueClass) { "Cannot build ${this::class.simpleName} for not multi-field value class: $valueClass" } + } + + val loweringRepresentation = MultiFieldValueClassTree.create(valueClass.defaultType, replacements) as InternalNode + + val leaves: List> = ArrayList>().apply { + loweringRepresentation.visit(object : MultiFieldValueClassTree.Visitor { + override fun visitLeaf(treeNode: Leaf) { + add(treeNode) + } + + override fun visitInternalNode(treeNode: InternalNode) { + treeNode.fields.forEach { it.node.visit(this) } + } + }) + }.also { if (it.size <= 1) error("${this::class.qualifiedName} for $valueClass must have multiple leaves") } + + val nodeFullNames: Map, Name> = + mutableMapOf, Name>().apply { + loweringRepresentation.visit(object : MultiFieldValueClassTree.Visitor { + val parts = mutableListOf() + override fun visitLeaf(treeNode: Leaf) { + put(treeNode, makeName()) + } + + override fun visitInternalNode(treeNode: InternalNode) { + if (parts.isNotEmpty()) { + put(treeNode, makeName()) + } + for (field in treeNode.fields) { + parts.add(field.name.asString()) + field.node.visit(this) + parts.removeLast() + } + } + + private fun makeName() = Name.guessByFirstCharacter(parts.joinToString("$")) + }) + } + + init { + require(nodeFullNames.size == nodeFullNames.values.distinct().size) { "Ambiguous names found: ${nodeFullNames.values}" } + } + + private val indexByLeaf = leaves.withIndex().associate { it.value to it.index } + private val indexesByInternalNode = mutableMapOf, IntRange>().apply { + var index = 0 + loweringRepresentation.visit(object : MultiFieldValueClassTree.Visitor { + override fun visitLeaf(treeNode: Leaf) { + index++ + } + + override fun visitInternalNode(treeNode: InternalNode) { + val start = index + treeNode.fields.forEach { it.node.visit(this) } + val finish = index + val range = start until finish + if (finish - start <= 1) { + error("Invalid multi-field value class indexes range for class $valueClass: ${range}") + } + put(treeNode, range) + } + }) + } + + val oldPrimaryConstructor = valueClass.primaryConstructor ?: error("Value classes have primary constructors") + + val fields = leaves.map { leaf -> + irFactory.buildField { + this.name = nodeFullNames[leaf]!! + this.type = leaf.type + visibility = DescriptorVisibilities.PRIVATE + } + } + + private val gettersVisibilities = mutableMapOf, DescriptorVisibility>().apply { + loweringRepresentation.visit(object : MultiFieldValueClassTree.Visitor { + val stack = mutableListOf() + override fun visitLeaf(treeNode: Leaf) { + put(treeNode, stack.last()) + } + + override fun visitInternalNode(treeNode: InternalNode) { + if (stack.isNotEmpty()) { + put(treeNode, stack.last()) + } + for (field in treeNode.fields) { + stack.add(field.visibility) + field.node.visit(this) + stack.pop() + } + } + }) + } + + private val gettersAnnotations = mutableMapOf, List>().apply { + loweringRepresentation.visit(object : MultiFieldValueClassTree.Visitor { + val stack = mutableListOf>() + override fun visitLeaf(treeNode: Leaf) { + put(treeNode, stack.last()) + } + + override fun visitInternalNode(treeNode: InternalNode) { + if (stack.isNotEmpty()) { + put(treeNode, stack.last()) + } + for (field in treeNode.fields) { + stack.add(field.annotations) + field.node.visit(this) + stack.pop() + } + } + }) + } + + fun IrBuilderWithScope.fieldGetter(receiver: IrValueParameter, field: IrField): IrGetField = + irGetField(irGet(receiver), field) + + fun IrFunction.fieldGetter(field: IrField): IrBuilderWithScope.() -> IrGetField = { fieldGetter(dispatchReceiverParameter!!, field) } + + val selfImplementationAgnosticDeclarations = ImplementationAgnostic( + fields.map { + VirtualProperty( + type = it.type, + makeGetter = { receiver: IrValueParameter -> irGetField(irGet(receiver), it) }, + assigner = { receiver: IrValueParameter, value: IrExpression -> irSetField(irGet(receiver), it, value) }, + ) + } + ) + + val primaryConstructor: IrConstructor = irFactory.buildConstructor { + updateFrom(oldPrimaryConstructor) + visibility = DescriptorVisibilities.PRIVATE + origin = JvmLoweredDeclarationOrigin.SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER + returnType = oldPrimaryConstructor.returnType + }.apply { + copyTypeParametersFrom(oldPrimaryConstructor) + addFlattenedClassRepresentationToParameters() + val irConstructor = this@apply + body = context.createIrBuilder(irConstructor.symbol).irBlockBody(irConstructor) { + +irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single()) + for (i in leaves.indices) { + +irSetField( + receiver = irGet(valueClass.thisReceiver!!), + field = fields[i], + value = irGet(irConstructor.valueParameters[i]) + ) + } + } + } + + val primaryConstructorImpl: IrSimpleFunction = irFactory.buildFun { + name = InlineClassAbi.mangledNameFor(oldPrimaryConstructor, false, false) + visibility = oldPrimaryConstructor.visibility + origin = JvmLoweredDeclarationOrigin.STATIC_MULTI_FIELD_VALUE_CLASS_CONSTRUCTOR + returnType = context.irBuiltIns.unitType + modality = Modality.FINAL + }.apply { + copyTypeParametersFrom(oldPrimaryConstructor) + addFlattenedClassRepresentationToParameters() + // body is added in Lowering file + } + + val boxMethod = irFactory.buildFun { + name = Name.identifier(KotlinTypeMapper.BOX_JVM_METHOD_NAME) + origin = JvmLoweredDeclarationOrigin.SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER + returnType = valueClass.defaultType + }.apply { + copyTypeParametersFrom(valueClass) + addFlattenedClassRepresentationToParameters() + // body is added in Lowering file + } + + private fun IrFunction.addFlattenedClassRepresentationToParameters() { + for (leaf in leaves) { + addValueParameter { + this.name = nodeFullNames[leaf]!! + this.type = leaf.type + } + } + } + + val specializedEqualsMethod = irFactory.buildFun { + name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME + // TODO: Revisit this once we allow user defined equals methods in inline classes. + origin = JvmLoweredDeclarationOrigin.MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD + returnType = context.irBuiltIns.booleanType + }.apply { + copyTypeParametersFrom(oldPrimaryConstructor) + for (leaf in leaves) { + addValueParameter { + this.name = Name.guessByFirstCharacter( + "${InlineClassDescriptorResolver.SPECIALIZED_EQUALS_FIRST_PARAMETER_NAME}$${nodeFullNames[leaf]!!.asString()}" + ) + this.type = leaf.type + } + } + for (leaf in leaves) { + addValueParameter { + this.name = Name.guessByFirstCharacter( + "${InlineClassDescriptorResolver.SPECIALIZED_EQUALS_SECOND_PARAMETER_NAME}$${nodeFullNames[leaf]!!.asString()}" + ) + this.type = leaf.type + } + } + // body is added in Lowering file + } + + val unboxMethods = fields.mapIndexed { index: Int, field: IrField -> + irFactory.buildFun { + name = Name.identifier(KotlinTypeMapper.UNBOX_JVM_METHOD_NAME + index) + origin = JvmLoweredDeclarationOrigin.SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER + returnType = field.type + }.apply { + parent = valueClass + createDispatchReceiverParameter() + body = with(context.createIrBuilder(this.symbol)) { + irExprBody(fieldGetter(field)()) + } + } + } + + val properties: Map, IrProperty> = + selfImplementationAgnosticDeclarations.nodeToExpressionGetters.filterKeys { it in nodeFullNames }.mapValues { (node, getter) -> + irFactory.buildProperty { + name = nodeFullNames[node]!! + origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + visibility = gettersVisibilities[node]!! + }.apply { + annotations = gettersAnnotations[node]!! + parent = valueClass + addGetter { + returnType = node.type + origin = IrDeclarationOrigin.GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER + }.apply { + createDispatchReceiverParameter() + body = with(context.createIrBuilder(this.symbol)) { + irExprBody(getter(this, dispatchReceiverParameter!!)) + } + } + } + } + + data class VirtualProperty(val type: IrType, val makeGetter: ExpressionGenerator, val assigner: ExpressionSupplier?) { + constructor(declaration: IrValueDeclaration) : this( + declaration.type, + { irGet(declaration) }, + if (declaration.isAssignable) { _, value -> irSet(declaration, value) } else null, + ) + + val isAssignable: Boolean + get() = assigner != null + + fun map(f: (R) -> T) = VirtualProperty( + type = type, + makeGetter = { makeGetter(f(it)) }, + assigner = assigner?.let { assigner -> { additionalParam, value -> assigner(f(additionalParam), value) } }, + ) + } + + fun implementationAgnosticFromDeclarations(declarations: List) = + ImplementationAgnostic(declarations.map { VirtualProperty(it) }) + + /** + * Multi-field value class instance can be not only a standalone instance but also: + * 1. A slice of fields in another multi-field class instance; + * 2. A slice of fields in another regular class instance; + * 3. Virtual instance constructed with local variables; + * 4. Virtual instance constructed with local parameters; + * 5. Virtual instance constructed with local fields; + * 6. Some mix of the above ones. + */ + inner class ImplementationAgnostic(val virtualFields: List>) { + val regularDeclarations = this@MultiFieldValueClassSpecificDeclarations + + fun map(f: (R) -> T) = ImplementationAgnostic(virtualFields.map { it.map(f) }) + + init { + require(virtualFields.size == leaves.size) { "Wrong symbols number given for $leaves, got $virtualFields" } + for ((actual, expected) in virtualFields.map { it.type } zip leaves.map { it.type }) { + require(actual.isSubtypeOf(expected, typeSystemContext)) { "$actual is not a subtype of $expected for MFVC $valueClass" } + } + } + + val nodeToSymbols = indexesByInternalNode.mapValues { (_, indexes) -> virtualFields.slice(indexes) } + + indexByLeaf.mapValues { (_, index) -> listOf(virtualFields[index]) } + + private val internalNodeToExpressionGetters: Map, ExpressionGenerator> = + indexesByInternalNode.mapValues { (node, indexes) -> + { additionalParameter -> + val arguments = virtualFields.slice(indexes).map { it.makeGetter(this, additionalParameter) } + val innerDeclarations = replacements.getDeclarations(node.irClass!!)!! + irCall(innerDeclarations.boxMethod).apply { + for (i in arguments.indices) { + putValueArgument(i, arguments[i]) + } + } + } + } + + private val leavesToExpressionGetters: Map, ExpressionGenerator> = + indexByLeaf.mapValues { (_, index) -> { virtualFields[index].makeGetter(this, it) } } + + val nodeToExpressionGetters: Map, ExpressionGenerator> = + internalNodeToExpressionGetters + leavesToExpressionGetters + + operator fun get(name: Name): Pair, ImplementationAgnostic?>? = + when (val field = loweringRepresentation[name]) { + null -> null + else -> nodeToExpressionGetters[field.node]!! to when (field.node) { + is Leaf -> null + is InternalNode -> { + val irClass = field.node.irClass!! + val declarations = replacements.getDeclarations(irClass)!! + declarations.ImplementationAgnostic(nodeToSymbols[field.node]!!) + } + } + } + + val boxedExpression = nodeToExpressionGetters[loweringRepresentation]!! + + // todo type parameters + // todo recursive + // todo cheeeck!!! + // todo equals + // todo toString + // todo hashCode + // todo default parameters + // todo annotations etc. + // todo inline in not value class + } +} + +typealias ExpressionGenerator = IrBuilderWithScope.(T) -> IrExpression +typealias ExpressionSupplier = IrBuilderWithScope.(T, IrExpression) -> IrStatement \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrTypeUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrTypeUtils.kt index 900520f5633..3aa08ae8b7a 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrTypeUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrTypeUtils.kt @@ -117,8 +117,11 @@ fun IrType.defaultValue(startOffset: Int, endOffset: Int, context: JvmBackendCon } } -fun IrType.isInlineClassType(): Boolean = - erasedUpperBound.isSingleFieldValueClass +fun IrType.isInlineClassType(): Boolean = erasedUpperBound.isSingleFieldValueClass + +fun IrType.isMultiFieldValueClassType(): Boolean = erasedUpperBound.isMultiFieldValueClass + +fun IrType.isValueClassType(): Boolean = erasedUpperBound.isValue val IrType.upperBound: IrType get() = erasedUpperBound.symbol.starProjectedType diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt index 1dbbb512852..b68ca90f2a4 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt @@ -256,6 +256,13 @@ val IrDeclaration.isStaticInlineClassReplacement: Boolean get() = origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_REPLACEMENT || origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_CONSTRUCTOR +val IrDeclaration.isStaticMultiFieldValueClassReplacement: Boolean + get() = origin == JvmLoweredDeclarationOrigin.STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT + || origin == JvmLoweredDeclarationOrigin.STATIC_MULTI_FIELD_VALUE_CLASS_CONSTRUCTOR + +val IrDeclaration.isStaticValueClassReplacement: Boolean + get() = isStaticMultiFieldValueClassReplacement || isStaticInlineClassReplacement + // On the IR backend we represent raw types as star projected types with a special synthetic annotation. // See `TypeTranslator.translateTypeAnnotations`. private fun JvmBackendContext.makeRawTypeAnnotation() = @@ -305,7 +312,7 @@ val IrClass.isSyntheticSingleton: Boolean fun IrSimpleFunction.suspendFunctionOriginal(): IrSimpleFunction = if (isSuspend && - !isStaticInlineClassReplacement && + !isStaticValueClassReplacement && !isOrOverridesDefaultParameterStub() && parentAsClass.origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS ) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt index 71cad44385f..ce08efd6bbc 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt @@ -422,7 +422,7 @@ class MethodSignatureMapper(private val context: JvmBackendContext, private val // TODO: get rid of this (probably via some special lowering) private fun mapOverriddenSpecialBuiltinIfNeeded(callee: IrFunction, superCall: Boolean): JvmMethodSignature? { // Do not remap calls to static replacements of inline class methods, since they have completely different signatures. - if (callee.isStaticInlineClassReplacement) return null + if (callee.isStaticValueClassReplacement) return null val overriddenSpecialBuiltinFunction = (callee.toIrBasedDescriptor().getOverriddenBuiltinReflectingJvmDescriptor() as IrBasedSimpleFunctionDescriptor?)?.owner if (overriddenSpecialBuiltinFunction != null && !superCall) { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt index cd00046c221..28a15bf3af4 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt @@ -43,6 +43,7 @@ interface IrDeclarationOrigin { object GENERATED_DATA_CLASS_MEMBER : IrDeclarationOriginImpl("GENERATED_DATA_CLASS_MEMBER") object GENERATED_SINGLE_FIELD_VALUE_CLASS_MEMBER : IrDeclarationOriginImpl("GENERATED_SINGLE_FIELD_VALUE_CLASS_MEMBER") object GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER : IrDeclarationOriginImpl("GENERATED_MULTI_FIELD_VALUE_CLASS_MEMBER") + object GENERATED_MULTI_FIELD_VALUE_CLASS_PARAMETER : IrDeclarationOriginImpl("GENERATED_MULTI_FIELD_VALUE_CLASS_PARAMETER") object LOCAL_FUNCTION : IrDeclarationOriginImpl("LOCAL_FUNCTION") object LOCAL_FUNCTION_FOR_LAMBDA : IrDeclarationOriginImpl("LOCAL_FUNCTION_FOR_LAMBDA") object CATCH_PARAMETER : IrDeclarationOriginImpl("CATCH_PARAMETER") diff --git a/compiler/testData/codegen/box/valueClasses/simple.kt b/compiler/testData/codegen/box/valueClasses/simple.kt new file mode 100644 index 00000000000..ef367151f23 --- /dev/null +++ b/compiler/testData/codegen/box/valueClasses/simple.kt @@ -0,0 +1,134 @@ +// CHECK_BYTECODE_LISTING +// WITH_STDLIB +// TARGET_BACKEND: JVM_IR +// WORKS_WHEN_VALUE_CLASS +// LANGUAGE: +ValueClasses + +@JvmInline +value class A(val x: List) + +@JvmInline +value class B(val x: UInt) + +@JvmInline +value class C(val x: Int, val y: B, val z: String = "3") + +@JvmInline +value class D(val x: C) { + constructor(x: Int, y: UInt, z: Int) : this(C(x, B(y), z.toString())) + + init { + println(x.x) + } +} + +inline fun inlined(x: Int, y: UInt, z: Int): D { + return D(C(x, B(y), z.toString())) +} + +fun notInlined(x: Int, y: UInt, z: Int) = D(C(x, B(y), z.toString())) + +@JvmInline +value class E(val x: D) { + var withNonTrivialSetters: D + get() = TODO() + set(_) = TODO() +} + +@JvmInline +value class R(val x: Int, val y: UInt, val z: E, val t: A) + +fun > f(r: R) { + println(r) + println(r.x) + println(r.y) + println(r.z) + println(r.t) + println(r.t.x) + println(r.z.x) + println(r.z.x.x) + println(r.z.x.x.x) + println(r.z.x.x.y) + println(r.z.x.x.z) + println(r.z.x.x.y.x) +} + +fun g(e: E) { +} + +fun > h(r: R) { + g(r.z) + f(r) + r + C(2, B(3U), "") // todo fix box + D(C(2, B(3U), "")) + val x = D(C(2, B(3U), "")) + var y = D(C(4, B(5U), "1")) + println(y) + y = D(C(6, B(7U), "2")) + y = D(6, 7U, 2) + y = inlined(6, 7U, 2) + y = notInlined(6, 7U, 2) + println(y) +} + +fun h1() { + var y = inlined(1, 2U, 3) // todo fix box + println(y) + y = inlined(4, 5U, 6) + println(y) +} + +class NotInlined(var l: R>, var y: Int) { + override fun toString(): String = l.toString() + l.z.x.x.z + + init { + l = l + } + + fun trySetter() { + l = l + } + + var withNonTrivialSetters: R> + get() = TODO() + set(_) = TODO() + + var withNonTrivialSettersWithBF: R> = l + get() { + println("1") + field + field.t + field == field + return field + } + set(value) { + println("3") + field = value + field = field + println("4") + } + + val withNonTrivialGettersWithBF: R> = l + get() { + println("1") + field + field.t + field == field + return field + } +} + +fun ekeke(x: NotInlined) { + x.l.toString() + var y = x.l + y.toString() + y = x.l + println(y) + x.l = x.l + x.l = R>(x.l.x, x.l.y, x.l.z, x.l.t) +} + +// todo add default parameters + +fun box() = "bad"//.also { h(R(1, 2U, E(D(C(3, B(4U), "5"))), A(listOf(listOf(6))))) } diff --git a/compiler/testData/codegen/box/valueClasses/simple.txt b/compiler/testData/codegen/box/valueClasses/simple.txt new file mode 100644 index 00000000000..cebd031dc0b --- /dev/null +++ b/compiler/testData/codegen/box/valueClasses/simple.txt @@ -0,0 +1,181 @@ +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class A { + // source: 'simple.kt' + private final @org.jetbrains.annotations.NotNull field x: java.util.List + private synthetic method (p0: java.util.List): void + public synthetic final static method box-impl(p0: java.util.List): A + public static @org.jetbrains.annotations.NotNull method constructor-impl(@org.jetbrains.annotations.NotNull p0: java.util.List): java.util.List + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: java.util.List, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: java.util.List, p1: java.util.List): boolean + public final @org.jetbrains.annotations.NotNull method getX(): java.util.List + public method hashCode(): int + public static method hashCode-impl(p0: java.util.List): int + public method toString(): java.lang.String + public static method toString-impl(p0: java.util.List): java.lang.String + public synthetic final method unbox-impl(): java.util.List +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class B { + // source: 'simple.kt' + private final field x: int + private synthetic method (p0: int): void + public synthetic final static method box-impl(p0: int): B + public static method constructor-impl(p0: int): int + public method equals(p0: java.lang.Object): boolean + public static method equals-impl(p0: int, p1: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int): boolean + public final method getX-pVg5ArA(): int + public method hashCode(): int + public static method hashCode-impl(p0: int): int + public method toString(): java.lang.String + public static method toString-impl(p0: int): java.lang.String + public synthetic final method unbox-impl(): int +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class C { + // source: 'simple.kt' + private field x: int + private field y: int + private @org.jetbrains.annotations.NotNull field z: java.lang.String + private synthetic method (p0: int, p1: int, p2: java.lang.String): void + public synthetic final static method box-impl(p0: int, p1: int, p2: java.lang.String): C + public final static method constructor-impl(p0: int, p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public static method equals-impl(p0: int, p1: int, p2: java.lang.String, p3: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int, p2: java.lang.String, p3: int, p4: int, p5: java.lang.String): boolean + public final method getX(): int + public final method getY(): int + public final @org.jetbrains.annotations.NotNull method getZ(): java.lang.String + public static method hashCode-impl(p0: int, p1: int, p2: java.lang.String): int + public static method toString-impl(p0: int, p1: int, p2: java.lang.String): java.lang.String + public synthetic final method unbox-impl0(): int + public synthetic final method unbox-impl1(): int + public synthetic final method unbox-impl2(): java.lang.String +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class D { + // source: 'simple.kt' + private field x$x: int + private field x$y: int + private @org.jetbrains.annotations.NotNull field x$z: java.lang.String + private synthetic method (p0: int, p1: int, p2: java.lang.String): void + public synthetic final static method box-impl(p0: int, p1: int, p2: java.lang.String): D + public final static method constructor-impl(p0: int, p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public final static @org.jetbrains.annotations.NotNull method constructor-impl(p0: int, p1: int, p2: int): D + public static method equals-impl(p0: int, p1: int, p2: java.lang.String, p3: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int, p2: java.lang.String, p3: int, p4: int, p5: java.lang.String): boolean + public final method getX$x(): int + public final method getX$y(): int + public final @org.jetbrains.annotations.NotNull method getX$z(): java.lang.String + public final @org.jetbrains.annotations.NotNull method getX(): C + public static method hashCode-impl(p0: int, p1: int, p2: java.lang.String): int + public static method toString-impl(p0: int, p1: int, p2: java.lang.String): java.lang.String + public synthetic final method unbox-impl0(): int + public synthetic final method unbox-impl1(): int + public synthetic final method unbox-impl2(): java.lang.String +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class E { + // source: 'simple.kt' + private field x$x$x: int + private field x$x$y: int + private @org.jetbrains.annotations.NotNull field x$x$z: java.lang.String + private synthetic method (p0: int, p1: int, p2: java.lang.String): void + public synthetic final static method box-impl(p0: int, p1: int, p2: java.lang.String): E + public final static method constructor-impl(p0: int, p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public static method equals-impl(p0: int, p1: int, p2: java.lang.String, p3: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int, p2: java.lang.String, p3: int, p4: int, p5: java.lang.String): boolean + public final method getX$x$x(): int + public final method getX$x$y(): int + public final @org.jetbrains.annotations.NotNull method getX$x$z(): java.lang.String + public final @org.jetbrains.annotations.NotNull method getX$x(): C + public final @org.jetbrains.annotations.NotNull method getX(): D + public static method hashCode-impl(p0: int, p1: int, p2: java.lang.String): int + public static method toString-impl(p0: int, p1: int, p2: java.lang.String): java.lang.String + public synthetic final method unbox-impl0(): int + public synthetic final method unbox-impl1(): int + public synthetic final method unbox-impl2(): java.lang.String +} + +@kotlin.Metadata +public final class NotInlined { + // source: 'simple.kt' + private @org.jetbrains.annotations.NotNull field l$t: java.util.List + private field l$y: int + private field l$z$x$x$x$1: int + private field l$z$x$x$x: int + private field l$z$x$x$y: int + private @org.jetbrains.annotations.NotNull field l$z$x$x$z: java.lang.String + private field y: int + private method (p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List, p6: int): void + public synthetic method (p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List, p6: int, p7: kotlin.jvm.internal.DefaultConstructorMarker): void + public final @org.jetbrains.annotations.NotNull method getL$t(): java.util.List + public final method getL$y(): int + public final method getL$z$x$x$x(): int + public final method getL$z$x$x$y(): int + public final @org.jetbrains.annotations.NotNull method getL$z$x$x$z(): java.lang.String + public final @org.jetbrains.annotations.NotNull method getL$z$x$x(): C + public final @org.jetbrains.annotations.NotNull method getL$z$x(): D + public final @org.jetbrains.annotations.NotNull method getL$z(): E + public final @org.jetbrains.annotations.NotNull method getL(): R + public final method getY(): int + public final method setL-Y_3tvh0(p0: int, p1: int, p2: int, p3: int, @org.jetbrains.annotations.NotNull p4: java.lang.String, @org.jetbrains.annotations.NotNull p5: java.util.List): void + public final method setY(p0: int): void + public @org.jetbrains.annotations.NotNull method toString(): java.lang.String + public final method trySetter(): void +} + +@kotlin.jvm.JvmInline +@kotlin.Metadata +public final class R { + // source: 'simple.kt' + private @org.jetbrains.annotations.NotNull field t: java.util.List + private field y: int + private field z$x$x$x$1: int + private field z$x$x$x: int + private field z$x$x$y: int + private @org.jetbrains.annotations.NotNull field z$x$x$z: java.lang.String + private synthetic method (p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List): void + public synthetic final static method box-impl(p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List): R + public final static method constructor-impl(p0: int, p1: int, p2: int, p3: int, @org.jetbrains.annotations.NotNull p4: java.lang.String, @org.jetbrains.annotations.NotNull p5: java.util.List): void + public static method equals-impl(p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List, p6: java.lang.Object): boolean + public final static method equals-impl0(p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List, p6: int, p7: int, p8: int, p9: int, p10: java.lang.String, p11: java.util.List): boolean + public final @org.jetbrains.annotations.NotNull method getT(): java.util.List + public final method getY(): int + public final method getZ$x$x$x(): int + public final method getZ$x$x$y(): int + public final @org.jetbrains.annotations.NotNull method getZ$x$x$z(): java.lang.String + public final @org.jetbrains.annotations.NotNull method getZ$x$x(): C + public final @org.jetbrains.annotations.NotNull method getZ$x(): D + public final @org.jetbrains.annotations.NotNull method getZ(): E + public static method hashCode-impl(p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List): int + public static method toString-impl(p0: int, p1: int, p2: int, p3: int, p4: java.lang.String, p5: java.util.List): java.lang.String + public synthetic final method unbox-impl0(): int + public synthetic final method unbox-impl1(): int + public synthetic final method unbox-impl2(): int + public synthetic final method unbox-impl3(): int + public synthetic final method unbox-impl4(): java.lang.String + public synthetic final method unbox-impl5(): java.util.List +} + +@kotlin.Metadata +public final class SimpleKt { + // source: 'simple.kt' + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String + public final static method ekeke(@org.jetbrains.annotations.NotNull p0: NotInlined): void + public final static method f-sUp7gFk-Y_3tvh0(p0: int, p1: int, p2: int, p3: int, @org.jetbrains.annotations.NotNull p4: java.lang.String, @org.jetbrains.annotations.NotNull p5: java.util.List): void + public final static method g-sUp7gFk-ZTiL54U(p0: int, p1: int, @org.jetbrains.annotations.NotNull p2: java.lang.String): void + public final static method h-sUp7gFk-Y_3tvh0(p0: int, p1: int, p2: int, p3: int, @org.jetbrains.annotations.NotNull p4: java.lang.String, @org.jetbrains.annotations.NotNull p5: java.util.List): void + public final static method h1(): void + public final static @org.jetbrains.annotations.NotNull method inlined-OsBMiQA(p0: int, p1: int, p2: int): D + public final static @org.jetbrains.annotations.NotNull method notInlined-OsBMiQA(p0: int, p1: int, p2: int): D +} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 794159fe5cb..fd557d143a8 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -50194,6 +50194,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes public void testEquality() throws Exception { runTest("compiler/testData/codegen/box/valueClasses/equality.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); } + + @Test + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + runTest("compiler/testData/codegen/box/valueClasses/simple.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); + } } @Nested