JVM_IR: generate more accessors for use in inline functions
This commit is contained in:
+28
-47
@@ -7,8 +7,6 @@ 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.IrElementVisitorVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.ScopeWithIr
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyValueParametersToStatic
|
||||
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
|
||||
@@ -20,6 +18,7 @@ import org.jetbrains.kotlin.backend.jvm.intrinsics.receiverAndArgs
|
||||
import org.jetbrains.kotlin.codegen.OwnerKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
@@ -30,12 +29,10 @@ import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrNull
|
||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||
import org.jetbrains.kotlin.ir.types.isSubtypeOfClass
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.load.java.JavaVisibilities
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
@@ -48,10 +45,8 @@ internal val syntheticAccessorPhase = makeIrFilePhase(
|
||||
|
||||
private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
||||
private val pendingTransformations = mutableListOf<Function0<Unit>>()
|
||||
private val inlinedLambdasCollector = InlinedLambdasCollector()
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.acceptVoid(inlinedLambdasCollector)
|
||||
irFile.transformChildrenVoid(this)
|
||||
pendingTransformations.forEach { it() }
|
||||
}
|
||||
@@ -110,6 +105,14 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
else -> error("Unknown subclass of IrFunctionSymbol")
|
||||
}
|
||||
|
||||
// In case of Java `protected static`, access could be done from a public inline function in the same package,
|
||||
// requiring an accessor, which we cannot add to the Java class.
|
||||
private fun IrDeclarationWithVisibility.accessorParent() =
|
||||
if (visibility == JavaVisibilities.PROTECTED_STATIC_VISIBILITY)
|
||||
currentClass!!.irElement as IrClass
|
||||
else
|
||||
parent
|
||||
|
||||
private fun IrConstructor.makeConstructorAccessor(): IrConstructor {
|
||||
val source = this
|
||||
|
||||
@@ -161,7 +164,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
// TODO: Handle the exception after targeting Java 8 or newer, where JVM default methods are available.
|
||||
context.declarationFactory.getDefaultImplsClass(source.parent as IrClass)
|
||||
} else {
|
||||
source.parent
|
||||
source.accessorParent()
|
||||
}
|
||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||
|
||||
@@ -202,7 +205,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
modality = Modality.FINAL
|
||||
returnType = fieldSymbol.owner.type
|
||||
}.also { accessor ->
|
||||
accessor.parent = fieldSymbol.owner.parent
|
||||
accessor.parent = fieldSymbol.owner.accessorParent()
|
||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||
|
||||
if (!fieldSymbol.owner.isStatic) {
|
||||
@@ -238,7 +241,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
modality = Modality.FINAL
|
||||
returnType = context.irBuiltIns.unitType
|
||||
}.also { accessor ->
|
||||
accessor.parent = fieldSymbol.owner.parent
|
||||
accessor.parent = fieldSymbol.owner.accessorParent()
|
||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||
|
||||
if (!fieldSymbol.owner.isStatic) {
|
||||
@@ -406,6 +409,14 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
return Name.identifier("access\$prop\$$setterName")
|
||||
}
|
||||
|
||||
private val Visibility.isPrivate
|
||||
get() = Visibilities.isPrivate(this)
|
||||
|
||||
private val Visibility.isProtected
|
||||
get() = this == Visibilities.PROTECTED ||
|
||||
this == JavaVisibilities.PROTECTED_AND_PACKAGE ||
|
||||
this == JavaVisibilities.PROTECTED_STATIC_VISIBILITY
|
||||
|
||||
private fun IrSymbol.isAccessible(withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean {
|
||||
/// We assume that IR code that reaches us has been checked for correctness at the frontend.
|
||||
/// This function needs to single out those cases where Java accessibility rules differ from Kotlin's.
|
||||
@@ -418,24 +429,25 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
// There is never a problem with visibility of inline functions, as those don't end up as Java entities
|
||||
if (declaration is IrFunction && declaration.isInline) return true
|
||||
|
||||
// The only two visibilities where Kotlin rules differ from JVM rules.
|
||||
if (!withSuper && !Visibilities.isPrivate(declaration.visibility) && declaration.visibility != Visibilities.PROTECTED) return true
|
||||
// `internal` maps to public and requires no accessor.
|
||||
if (!withSuper && !declaration.visibility.isPrivate && !declaration.visibility.isProtected) return true
|
||||
|
||||
// If local variables are accessible by Kotlin rules, they also are by Java rules.
|
||||
val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true
|
||||
|
||||
// Within inline functions, we have to assume the worst.
|
||||
if (inlinedLambdasCollector.isInlineNonpublicContext(allScopes))
|
||||
val function = currentFunction?.irElement as IrFunction?
|
||||
if (function?.isInline == true && !function.visibility.isPrivate && (withSuper || !function.visibility.isProtected))
|
||||
return false
|
||||
|
||||
val contextDeclarationContainer = allScopes.lastOrNull { it.irElement is IrDeclarationContainer }?.irElement
|
||||
|
||||
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer?.getPackageFragment()?.fqName
|
||||
return when {
|
||||
Visibilities.isPrivate(declaration.visibility) && symbolDeclarationContainer != contextDeclarationContainer -> false
|
||||
(declaration.visibility == Visibilities.PROTECTED && !samePackage &&
|
||||
declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false
|
||||
declaration.visibility.isProtected && !samePackage &&
|
||||
!(symbolDeclarationContainer is IrClass && contextDeclarationContainer is IrClass &&
|
||||
contextDeclarationContainer.isSubclassOf(symbolDeclarationContainer))) -> false
|
||||
contextDeclarationContainer.isSubclassOf(symbolDeclarationContainer)) -> false
|
||||
// Invoking with super qualifier is implemented by invokespecial, which requires
|
||||
// 1. `this` to be assign compatible with current class.
|
||||
// 2. the method is a member of a superclass of current class.
|
||||
@@ -446,34 +458,3 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InlinedLambdasCollector : IrElementVisitorVoidWithContext() {
|
||||
private val inlinedLambdasInNonPublicContexts = mutableSetOf<IrFunction>()
|
||||
|
||||
fun isInlineNonpublicContext(scopeStack: List<ScopeWithIr>): Boolean {
|
||||
val currentFunction = scopeStack.map { it.irElement }.lastOrNull { it is IrFunction } as? IrFunction ?: return false
|
||||
return (currentFunction.isInline && currentFunction.visibility in setOf(Visibilities.PRIVATE, Visibilities.PROTECTED))
|
||||
|| currentFunction in inlinedLambdasInNonPublicContexts
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
|
||||
val callTarget = expression.symbol.owner
|
||||
if (callTarget.isInline && isInlineNonpublicContext(allScopes)) {
|
||||
for (i in 0 until expression.valueArgumentsCount) {
|
||||
val argument = expression.getValueArgument(i)?.removeBlocks()
|
||||
if (argument is IrFunctionReference &&
|
||||
argument.symbol.owner.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA &&
|
||||
!callTarget.valueParameters[i].isNoinline
|
||||
) {
|
||||
inlinedLambdasInNonPublicContexts.add(argument.symbol.owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitFunctionAccess(expression)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// FILE: JavaClass.java
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_REFLECT
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// The non-IR backend attempts to call a non-existent accessor in class Test.
|
||||
// IGNORE_BACKEND: JVM
|
||||
|
||||
// FILE: Test.java
|
||||
|
||||
public class Test {
|
||||
protected static String testStatic() {
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
class Test2 {
|
||||
inline fun test() = Test.testStatic()
|
||||
}
|
||||
|
||||
// FILE: test2.kt
|
||||
|
||||
package anotherPackage
|
||||
|
||||
import Test2
|
||||
|
||||
fun box() = Test2().test()
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// TARGET_BACKEND: JVM
|
||||
|
||||
// FILE: First.java
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
package test
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JVM_IR
|
||||
// FILE: 1.kt
|
||||
|
||||
package test
|
||||
|
||||
+5
@@ -23813,6 +23813,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethod.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineCallsStaticMethodFromOtherPackage.kt")
|
||||
public void testInlineCallsStaticMethodFromOtherPackage() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethodFromOtherPackage.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("kt8089.kt")
|
||||
public void testKt8089() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
||||
|
||||
+5
@@ -23755,6 +23755,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class Statics extends AbstractLightAnalysisModeTest {
|
||||
@TestMetadata("inlineCallsStaticMethodFromOtherPackage.kt")
|
||||
public void ignoreInlineCallsStaticMethodFromOtherPackage() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethodFromOtherPackage.kt");
|
||||
}
|
||||
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
|
||||
}
|
||||
|
||||
+5
@@ -23833,6 +23833,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethod.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineCallsStaticMethodFromOtherPackage.kt")
|
||||
public void testInlineCallsStaticMethodFromOtherPackage() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethodFromOtherPackage.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("kt8089.kt")
|
||||
public void testKt8089() throws Exception {
|
||||
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
||||
|
||||
Reference in New Issue
Block a user