K2 JVM IR: fix access to Java field shadowed by Kotlin property
#KT-56386 Fixed Co-authored-by: Mikhail Glukhikh <Mikhail.Glukhikh@jetbrains.com>
This commit is contained in:
@@ -376,6 +376,7 @@ private val jvmFilePhases = listOf(
|
||||
replaceKFunctionInvokeWithFunctionInvokePhase,
|
||||
kotlinNothingValueExceptionPhase,
|
||||
makePropertyDelegateMethodsStaticPhase,
|
||||
addSuperQualifierToJavaFieldAccessPhase,
|
||||
|
||||
renameFieldsPhase,
|
||||
fakeInliningLocalVariablesLowering,
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2010-2023 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.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFieldAccessExpression
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.util.isClass
|
||||
import org.jetbrains.kotlin.ir.util.isEnumClass
|
||||
import org.jetbrains.kotlin.ir.util.isFromJava
|
||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
|
||||
internal val addSuperQualifierToJavaFieldAccessPhase = makeIrFilePhase(
|
||||
{ context ->
|
||||
if (context.state.configuration.getBoolean(CommonConfigurationKeys.USE_FIR)) {
|
||||
AddSuperQualifierToJavaFieldAccessLowering
|
||||
} else {
|
||||
FileLoweringPass.Empty
|
||||
}
|
||||
},
|
||||
name = "AddSuperQualifierToJavaFieldAccess",
|
||||
description = "Make `\$delegate` methods for optimized delegated properties static",
|
||||
// Property references need to be lowered to classes with field access inside. We can't perform this phase on unlowered property
|
||||
// references as IrPropertyReference doesn't have superQualifierSymbol.
|
||||
prerequisite = setOf(propertyReferencePhase),
|
||||
)
|
||||
|
||||
// This lowering changes field accesses so that codegen will correctly generate access to a public field in a Java superclass of a Kotlin
|
||||
// class, even if that field is "shadowed" by a property in the Kotlin class, with a _private_ field with the same name.
|
||||
// See KT-49507 and KT-48954 as good examples for cases we try to handle here.
|
||||
private object AddSuperQualifierToJavaFieldAccessLowering : IrElementVisitorVoid, FileLoweringPass {
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.accept(this, null)
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildren(this, null)
|
||||
}
|
||||
|
||||
override fun visitFieldAccess(expression: IrFieldAccessExpression) {
|
||||
val dispatchReceiver = expression.receiver
|
||||
if (dispatchReceiver != null) {
|
||||
expression.superQualifierSymbol = superQualifierSymbolForField(dispatchReceiver, expression.symbol.owner)?.symbol
|
||||
}
|
||||
super.visitFieldAccess(expression)
|
||||
}
|
||||
|
||||
// Note that we're abusing superQualifierSymbol to make codegen generate the correct field owner in the bytecode.
|
||||
// However, currently there seems to be no better way to support it.
|
||||
private fun superQualifierSymbolForField(dispatchReceiver: IrExpression, field: IrField): IrClass? {
|
||||
if (field.correspondingPropertySymbol != null) return null
|
||||
val originalContainingClass = field.parentClassOrNull ?: return null
|
||||
val dispatchReceiverRepresentativeClass = dispatchReceiver.type.erasedUpperBound
|
||||
// Find first Java super class to avoid possible visibility exposure & separate compilation problems
|
||||
return getJavaFieldContainingClassSymbol(dispatchReceiverRepresentativeClass, originalContainingClass)
|
||||
}
|
||||
|
||||
// Note: dispatchReceiverRepresentativeClassifierSymbol here is the use-site receiver class,
|
||||
// and originalContainingClass is the class which contains Java field we are trying to access
|
||||
// ! Interfaces are out of our interests here !
|
||||
// This function returns a class symbol which:
|
||||
// - is the most derived Java class in hierarchy which has no Kotlin base classes (including transitive ones)
|
||||
// E.g. K2 <: J3 <: K1 <: J2 <: J1 ==> J2 is chosen
|
||||
// We shouldn't allow base Kotlin classes to avoid possible clashes with invisible properties inside
|
||||
private fun getJavaFieldContainingClassSymbol(dispatchReceiverRepresentativeClass: IrClass, originalContainingClass: IrClass): IrClass =
|
||||
findMostSpecificJavaClassWithoutKotlinSuperclasses(
|
||||
dispatchReceiverRepresentativeClass, originalContainingClass
|
||||
)?.resultingClass ?: originalContainingClass
|
||||
|
||||
private fun findMostSpecificJavaClassWithoutKotlinSuperclasses(current: IrClass, top: IrClass): SearchResult? {
|
||||
if (current == top) return SearchResult(top, false)
|
||||
|
||||
val superClass = current.superTypes.firstNotNullOfOrNull { supertype ->
|
||||
supertype.getClass()?.takeIf { it.isClass || it.isEnumClass }
|
||||
} ?: return null
|
||||
|
||||
val result = findMostSpecificJavaClassWithoutKotlinSuperclasses(superClass, top) ?: return null
|
||||
return if (current.isFromJava()) {
|
||||
if (result.hasKotlinSuperclasses) result else SearchResult(current, false)
|
||||
} else {
|
||||
SearchResult(result.resultingClass, true)
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchResult(val resultingClass: IrClass, val hasKotlinSuperclasses: Boolean)
|
||||
}
|
||||
+4
@@ -1013,6 +1013,10 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
|
||||
// in the anonymous class for this callable reference. Technically it's incorrect because this field is not declared here. It would
|
||||
// be more correct to create a fake override but that seems like more work for no clear benefit. Codegen will generate the correct
|
||||
// field access anyway, even if the field is not present in this parent.
|
||||
// Note that it is necessary to generate an access to the field whose parent is this anonymous class, and NOT some supertype like
|
||||
// k.j.i.CallableReference, or k.j.i.FunctionReferenceImpl, because then AddSuperQualifierToJavaFieldAccess lowering would add
|
||||
// superQualifierSymbol, which would break inlining of bound function references, since inliner will not understand how to transform
|
||||
// this getfield instruction in the bytecode.
|
||||
internal fun IrClass.getReceiverField(context: JvmBackendContext): IrField =
|
||||
context.irFactory.buildField {
|
||||
name = Name.identifier("receiver")
|
||||
|
||||
Reference in New Issue
Block a user