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.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
|
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.copyTypeParametersFrom
|
||||||
import org.jetbrains.kotlin.backend.common.ir.copyValueParametersToStatic
|
import org.jetbrains.kotlin.backend.common.ir.copyValueParametersToStatic
|
||||||
import org.jetbrains.kotlin.backend.common.ir.passTypeArgumentsFrom
|
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.codegen.OwnerKind
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
|
import org.jetbrains.kotlin.descriptors.Visibility
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
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.expressions.impl.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.*
|
import org.jetbrains.kotlin.ir.symbols.*
|
||||||
import org.jetbrains.kotlin.ir.types.classifierOrNull
|
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.types.isSubtypeOfClass
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
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.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.load.java.JavaVisibilities
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
@@ -48,10 +45,8 @@ internal val syntheticAccessorPhase = makeIrFilePhase(
|
|||||||
|
|
||||||
private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElementTransformerVoidWithContext(), FileLoweringPass {
|
||||||
private val pendingTransformations = mutableListOf<Function0<Unit>>()
|
private val pendingTransformations = mutableListOf<Function0<Unit>>()
|
||||||
private val inlinedLambdasCollector = InlinedLambdasCollector()
|
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
irFile.acceptVoid(inlinedLambdasCollector)
|
|
||||||
irFile.transformChildrenVoid(this)
|
irFile.transformChildrenVoid(this)
|
||||||
pendingTransformations.forEach { it() }
|
pendingTransformations.forEach { it() }
|
||||||
}
|
}
|
||||||
@@ -110,6 +105,14 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
else -> error("Unknown subclass of IrFunctionSymbol")
|
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 {
|
private fun IrConstructor.makeConstructorAccessor(): IrConstructor {
|
||||||
val source = this
|
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.
|
// TODO: Handle the exception after targeting Java 8 or newer, where JVM default methods are available.
|
||||||
context.declarationFactory.getDefaultImplsClass(source.parent as IrClass)
|
context.declarationFactory.getDefaultImplsClass(source.parent as IrClass)
|
||||||
} else {
|
} else {
|
||||||
source.parent
|
source.accessorParent()
|
||||||
}
|
}
|
||||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||||
|
|
||||||
@@ -202,7 +205,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
modality = Modality.FINAL
|
modality = Modality.FINAL
|
||||||
returnType = fieldSymbol.owner.type
|
returnType = fieldSymbol.owner.type
|
||||||
}.also { accessor ->
|
}.also { accessor ->
|
||||||
accessor.parent = fieldSymbol.owner.parent
|
accessor.parent = fieldSymbol.owner.accessorParent()
|
||||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||||
|
|
||||||
if (!fieldSymbol.owner.isStatic) {
|
if (!fieldSymbol.owner.isStatic) {
|
||||||
@@ -238,7 +241,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
modality = Modality.FINAL
|
modality = Modality.FINAL
|
||||||
returnType = context.irBuiltIns.unitType
|
returnType = context.irBuiltIns.unitType
|
||||||
}.also { accessor ->
|
}.also { accessor ->
|
||||||
accessor.parent = fieldSymbol.owner.parent
|
accessor.parent = fieldSymbol.owner.accessorParent()
|
||||||
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
|
||||||
|
|
||||||
if (!fieldSymbol.owner.isStatic) {
|
if (!fieldSymbol.owner.isStatic) {
|
||||||
@@ -406,6 +409,14 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
return Name.identifier("access\$prop\$$setterName")
|
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 {
|
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.
|
/// 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.
|
/// 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
|
// 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
|
if (declaration is IrFunction && declaration.isInline) return true
|
||||||
|
|
||||||
// The only two visibilities where Kotlin rules differ from JVM rules.
|
// `internal` maps to public and requires no accessor.
|
||||||
if (!withSuper && !Visibilities.isPrivate(declaration.visibility) && declaration.visibility != Visibilities.PROTECTED) return true
|
if (!withSuper && !declaration.visibility.isPrivate && !declaration.visibility.isProtected) return true
|
||||||
|
|
||||||
// If local variables are accessible by Kotlin rules, they also are by Java rules.
|
// If local variables are accessible by Kotlin rules, they also are by Java rules.
|
||||||
val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true
|
val symbolDeclarationContainer = (declaration.parent as? IrDeclarationContainer) as? IrElement ?: return true
|
||||||
|
|
||||||
// Within inline functions, we have to assume the worst.
|
// 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
|
return false
|
||||||
|
|
||||||
val contextDeclarationContainer = allScopes.lastOrNull { it.irElement is IrDeclarationContainer }?.irElement
|
val contextDeclarationContainer = allScopes.lastOrNull { it.irElement is IrDeclarationContainer }?.irElement
|
||||||
|
|
||||||
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer?.getPackageFragment()?.fqName
|
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer?.getPackageFragment()?.fqName
|
||||||
return when {
|
return when {
|
||||||
Visibilities.isPrivate(declaration.visibility) && symbolDeclarationContainer != contextDeclarationContainer -> false
|
declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false
|
||||||
(declaration.visibility == Visibilities.PROTECTED && !samePackage &&
|
declaration.visibility.isProtected && !samePackage &&
|
||||||
!(symbolDeclarationContainer is IrClass && contextDeclarationContainer is IrClass &&
|
!(symbolDeclarationContainer is IrClass && contextDeclarationContainer is IrClass &&
|
||||||
contextDeclarationContainer.isSubclassOf(symbolDeclarationContainer))) -> false
|
contextDeclarationContainer.isSubclassOf(symbolDeclarationContainer)) -> false
|
||||||
// Invoking with super qualifier is implemented by invokespecial, which requires
|
// Invoking with super qualifier is implemented by invokespecial, which requires
|
||||||
// 1. `this` to be assign compatible with current class.
|
// 1. `this` to be assign compatible with current class.
|
||||||
// 2. the method is a member of a superclass of 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
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// FILE: JavaClass.java
|
// FILE: JavaClass.java
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
// WITH_REFLECT
|
// 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
|
// TARGET_BACKEND: JVM
|
||||||
|
|
||||||
// FILE: First.java
|
// FILE: First.java
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
package test
|
package test
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// FILE: 1.kt
|
// FILE: 1.kt
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|||||||
+5
@@ -23813,6 +23813,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/statics/inlineCallsStaticMethod.kt");
|
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")
|
@TestMetadata("kt8089.kt")
|
||||||
public void testKt8089() throws Exception {
|
public void testKt8089() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
||||||
|
|||||||
+5
@@ -23755,6 +23755,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
public static class Statics extends AbstractLightAnalysisModeTest {
|
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 {
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
|
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");
|
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")
|
@TestMetadata("kt8089.kt")
|
||||||
public void testKt8089() throws Exception {
|
public void testKt8089() throws Exception {
|
||||||
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
runTest("compiler/testData/codegen/box/statics/kt8089.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user