diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmPropertiesLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmPropertiesLowering.kt index 2caa587ae7d..85c17934c31 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmPropertiesLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/JvmPropertiesLowering.kt @@ -28,7 +28,6 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrFieldAccessExpression import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl import org.jetbrains.kotlin.ir.types.* -import org.jetbrains.kotlin.ir.types.impl.IrUninitializedType import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.load.java.JvmAbi @@ -164,7 +163,7 @@ class JvmPropertiesLowering(private val backendContext: JvmBackendContext) : IrE JvmLoweredDeclarationOrigin.SYNTHETIC_METHOD_FOR_PROPERTY_OR_TYPEALIAS_ANNOTATIONS, // TODO: technically JVM permits having fields with same name but different type, so we could potentially // generate two properties like that; should this be the getter's return type instead? - returnType = backendContext.irBuiltIns.unitType + isStatic = true, returnType = backendContext.irBuiltIns.unitType ).apply { body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET) annotations = declaration.annotations @@ -175,7 +174,8 @@ class JvmPropertiesLowering(private val backendContext: JvmBackendContext) : IrE declaration: IrProperty, suffix: String, origin: IrDeclarationOrigin, - returnType: IrType = IrUninitializedType + isStatic: Boolean, + returnType: IrType ) = irFactory.buildFun { name = Name.identifier(computeSyntheticMethodName(declaration, suffix)) modality = Modality.OPEN @@ -183,14 +183,28 @@ class JvmPropertiesLowering(private val backendContext: JvmBackendContext) : IrE this.origin = origin this.returnType = returnType }.apply { - declaration.getter?.extensionReceiverParameter?.let { - // Synthetic methods don't get generic type signatures anyway, so not exactly useful to preserve type parameters. - extensionReceiverParameter = it.copyTo(this, type = it.type.eraseTypeParameters()) - } + var index = 0 + valueParameters = listOfNotNull( + declaration.getter?.dispatchReceiverParameter?.takeIf { !isStatic }?.let { + // Synthetic methods don't get generic type signatures anyway, so not exactly useful to preserve type parameters. + it.copyTo(this, type = it.type.eraseTypeParameters(), index = index++) + }, + declaration.getter?.extensionReceiverParameter?.let { + it.copyTo(this, type = it.type.eraseTypeParameters(), index = index) + } + ) parent = declaration.parent metadata = declaration.metadata } + fun JvmBackendContext.createSyntheticMethodForPropertyDelegate(declaration: IrProperty) = + createSyntheticMethodForProperty( + declaration, + JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX, + IrDeclarationOrigin.PROPERTY_DELEGATE, + isStatic = false, returnType = irBuiltIns.anyNType + ) + private fun JvmBackendContext.computeSyntheticMethodName(property: IrProperty, suffix: String): String { val baseName = if (state.languageVersionSettings.supportsFeature(LanguageFeature.UseGetterNameForPropertyAnnotationsMethodOnJvm)) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt index e8da7e633ca..71ccdead6df 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceDelegationLowering.kt @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder +import org.jetbrains.kotlin.backend.jvm.lower.JvmPropertiesLowering.Companion.createSyntheticMethodForPropertyDelegate import org.jetbrains.kotlin.builtins.StandardNames import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.builders.* @@ -17,8 +19,10 @@ import org.jetbrains.kotlin.ir.builders.declarations.buildVariable import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrPropertyReferenceImpl import org.jetbrains.kotlin.ir.util.getPackageFragment import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.ir.util.transformDeclarationsFlat import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.name.Name @@ -80,29 +84,53 @@ private class PropertyReferenceDelegationTransformer(val context: JvmBackendCont private val IrStatement.isStdlibCall: Boolean get() = this is IrCall && symbol.owner.getPackageFragment()?.fqName == StandardNames.BUILT_INS_PACKAGE_FQ_NAME - override fun visitProperty(declaration: IrProperty): IrStatement { - if (!declaration.isDelegated || declaration.isFakeOverride) return super.visitProperty(declaration) + override fun visitClass(declaration: IrClass): IrStatement { + declaration.transformChildren(this, null) + declaration.transformDeclarationsFlat { + (it as? IrProperty)?.transform() + } + return declaration + } - val oldField = declaration.backingField + private fun IrProperty.transform(): List? { + if (!isDelegated || isFakeOverride) return null + + val oldField = backingField val delegate = oldField?.initializer?.expression if (delegate !is IrPropertyReference || - declaration.getter?.returnsResultOfStdlibCall == false || - declaration.setter?.returnsResultOfStdlibCall == false - ) return super.visitProperty(declaration) + getter?.returnsResultOfStdlibCall == false || + setter?.returnsResultOfStdlibCall == false + ) return null - declaration.backingField = (delegate.dispatchReceiver ?: delegate.extensionReceiver)?.let { receiver -> + backingField = (delegate.dispatchReceiver ?: delegate.extensionReceiver)?.let { receiver -> context.irFactory.buildField { updateFrom(oldField) - name = Name.identifier("${declaration.name}\$receiver") + name = Name.identifier("${this@transform.name}\$receiver") type = receiver.type }.apply { parent = oldField.parent initializer = context.irFactory.createExpressionBody(receiver.transform(this@PropertyReferenceDelegationTransformer, null)) } } - declaration.getter?.apply { body = accessorBody(delegate, declaration.backingField) } - declaration.setter?.apply { body = accessorBody(delegate, declaration.backingField) } - return declaration + getter?.apply { body = accessorBody(delegate, backingField) } + setter?.apply { body = accessorBody(delegate, backingField) } + + val delegateMethod = context.createSyntheticMethodForPropertyDelegate(this).apply { + body = context.createJvmIrBuilder(symbol).run { + val propertyOwner = if (getter?.dispatchReceiverParameter != null) irGet(valueParameters[0]) else null + val boundReceiver = backingField?.let { irGetField(propertyOwner, it) } + irExprBody(with(delegate) { + val origin = PropertyReferenceLowering.REFLECTED_PROPERTY_REFERENCE + IrPropertyReferenceImpl(startOffset, endOffset, type, symbol, typeArgumentsCount, field, getter, setter, origin) + }.apply { + when { + delegate.dispatchReceiver != null -> dispatchReceiver = boundReceiver + delegate.extensionReceiver != null -> extensionReceiver = boundReceiver + } + }) + } + } + return listOf(this, delegateMethod) } override fun visitLocalDelegatedProperty(declaration: IrLocalDelegatedProperty): IrStatement { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt index a6c23087b32..104d9afeec8 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/PropertyReferenceLowering.kt @@ -60,7 +60,10 @@ internal val propertyReferencePhase = makeIrFilePhase( prerequisite = setOf(functionReferencePhase, suspendLambdaPhase, propertyReferenceDelegationPhase) ) -private class PropertyReferenceLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass { +internal class PropertyReferenceLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass { + // Marking a property reference with this origin causes it to not generate a class. + object REFLECTED_PROPERTY_REFERENCE : IrStatementOriginImpl("REFLECTED_PROPERTY_REFERENCE") + // TODO: join IrLocalDelegatedPropertyReference and IrPropertyReference via the class hierarchy? private val IrMemberAccessExpression<*>.getter: IrSimpleFunctionSymbol? get() = (this as? IrPropertyReference)?.getter ?: (this as? IrLocalDelegatedPropertyReference)?.getter @@ -299,6 +302,8 @@ private class PropertyReferenceLowering(val context: JvmBackendContext) : IrElem private fun cachedKProperty(expression: IrCallableReference<*>): IrExpression { expression.transformChildrenVoid() + if (expression.origin == REFLECTED_PROPERTY_REFERENCE) + return createReflectedKProperty(expression) if (expression.origin != IrStatementOrigin.PROPERTY_REFERENCE_FOR_DELEGATE) return createSpecializedKProperty(expression) diff --git a/compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt b/compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt index 21edeabcfbf..21ad37b2230 100644 --- a/compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt +++ b/compiler/testData/codegen/bytecodeText/optimizedDelegatedProperties/delegateToAnother.kt @@ -30,9 +30,15 @@ fun local() { // 0 kotlin/jvm/internal/PropertyReference[0-2]Impl\.\ // JVM_IR_TEMPLATES -// Optimized all to direct accesses: -// 0 kotlin/jvm/internal/MutablePropertyReference[0-2]Impl\.\ +// Optimized all to direct accesses, with `$delegate` methods generating reflected references on demand: +// 0 extends kotlin/jvm/internal/MutablePropertyReference[0-2]Impl +// 0 private final( static)? Lkotlin/reflect/KMutableProperty[0-2]; [xyz]m?\$delegate +// 0 LOCALVARIABLE [xyz]m? Lkotlin/reflect/KMutableProperty[0-2]; +// 12 static get[XYZ]m?\$delegate // JVM_TEMPLATES -// Not optimized: -// 16 kotlin/jvm/internal/MutablePropertyReference[0-2]Impl\.\ +// Not optimized, references created as classes and stored in fields: +// 16 extends kotlin/jvm/internal/MutablePropertyReference[0-2]Impl +// 12 private final( static)? Lkotlin/reflect/KMutableProperty[0-2]; [xyz]m?\$delegate +// 4 LOCALVARIABLE [xyz]m? Lkotlin/reflect/KMutableProperty[0-2]; +// 0 static get[XYZ]m?\$delegate