JVM_IR: improve scope detection in accessor generation.
* Sometimes, there's a class after the last function in the scope stack: that's the local type's (non-inline) primary constructor. * In an inline (but not crossinline) lambda, accessors are needed if and only if they would be needed in the function that the lambda is defined in.
This commit is contained in:
+42
-17
@@ -7,6 +7,7 @@ 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.ir.copyTo
|
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.backend.jvm.ir.irArray
|
|||||||
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
|
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
|
||||||
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.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.IrStatement
|
import org.jetbrains.kotlin.ir.IrStatement
|
||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
|
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
|
||||||
@@ -33,10 +35,10 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
|
|||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
|
||||||
|
|
||||||
internal val callableReferencePhase = makeIrFilePhase(
|
internal val callableReferencePhase = makeIrFilePhase(
|
||||||
::CallableReferenceLowering,
|
::CallableReferenceLowering,
|
||||||
@@ -44,18 +46,19 @@ internal val callableReferencePhase = makeIrFilePhase(
|
|||||||
description = "Handle callable references"
|
description = "Handle callable references"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Originally copied from K/Native
|
private val IrStatementOrigin?.isLambda
|
||||||
internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION
|
||||||
// This pass ignores suspend function references and function references used in inline arguments to inline functions.
|
|
||||||
private val ignoredFunctionReferences = mutableSetOf<IrFunctionReference>()
|
|
||||||
|
|
||||||
private val IrFunctionReference.isIgnored: Boolean
|
internal class InlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitorVoidWithContext() {
|
||||||
get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this)
|
val inlineReferences = mutableSetOf<IrFunctionReference>()
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) = irFile.transformChildrenVoid(this)
|
// For crossinline lambdas, the call site is null as it's probably in a separate class somewhere.
|
||||||
|
// All other lambdas are guaranteed to be inlined into the scope they are declared in.
|
||||||
|
val lambdaToCallSite = mutableMapOf<IrFunction, IrDeclaration?>()
|
||||||
|
|
||||||
// Mark function references appearing as inlined arguments to inline functions.
|
override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this)
|
||||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
|
|
||||||
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
|
||||||
val function = expression.symbol.owner
|
val function = expression.symbol.owner
|
||||||
if (function.isInlineFunctionCall(context)) {
|
if (function.isInlineFunctionCall(context)) {
|
||||||
for (parameter in function.valueParameters) {
|
for (parameter in function.valueParameters) {
|
||||||
@@ -66,16 +69,41 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
if (!isInlineIrExpression(valueArgument))
|
if (!isInlineIrExpression(valueArgument))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (valueArgument is IrFunctionReference) {
|
val reference = when (valueArgument) {
|
||||||
ignoredFunctionReferences.add(valueArgument)
|
is IrFunctionReference -> valueArgument
|
||||||
} else if (valueArgument is IrBlock) {
|
is IrBlock -> valueArgument.statements.filterIsInstance<IrFunctionReference>().singleOrNull()
|
||||||
ignoredFunctionReferences.addIfNotNull(valueArgument.statements.filterIsInstance<IrFunctionReference>().singleOrNull())
|
else -> null
|
||||||
|
} ?: continue
|
||||||
|
|
||||||
|
inlineReferences.add(reference)
|
||||||
|
if (valueArgument is IrBlock && valueArgument.origin.isLambda) {
|
||||||
|
lambdaToCallSite[reference.symbol.owner] =
|
||||||
|
if (parameter.isCrossinline) null else currentScope!!.irElement as IrDeclaration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visitFunctionAccess(expression)
|
return super.visitFunctionAccess(expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun scan(context: JvmBackendContext, element: IrElement) =
|
||||||
|
InlineReferenceLocator(context).apply { element.accept(this, null) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Originally copied from K/Native
|
||||||
|
internal class CallableReferenceLowering(private val context: JvmBackendContext) : FileLoweringPass, IrElementTransformerVoidWithContext() {
|
||||||
|
// This pass ignores suspend function references and function references used in inline arguments to inline functions.
|
||||||
|
private val ignoredFunctionReferences = mutableSetOf<IrFunctionReference>()
|
||||||
|
|
||||||
|
private val IrFunctionReference.isIgnored: Boolean
|
||||||
|
get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this)
|
||||||
|
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
ignoredFunctionReferences.addAll(InlineReferenceLocator.scan(context, irFile).inlineReferences)
|
||||||
|
irFile.transformChildrenVoid(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun visitBlock(expression: IrBlock): IrExpression {
|
override fun visitBlock(expression: IrBlock): IrExpression {
|
||||||
if (!expression.origin.isLambda)
|
if (!expression.origin.isLambda)
|
||||||
return super.visitBlock(expression)
|
return super.visitBlock(expression)
|
||||||
@@ -403,9 +431,6 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val IrStatementOrigin?.isLambda
|
|
||||||
get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION
|
|
||||||
|
|
||||||
private val currentDeclarationParent
|
private val currentDeclarationParent
|
||||||
get() = allScopes.last { it.irElement is IrDeclarationParent }.irElement as IrDeclarationParent
|
get() = allScopes.last { it.irElement is IrDeclarationParent }.irElement as IrDeclarationParent
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-8
@@ -45,8 +45,10 @@ 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 inlineLambdaToCallSite = mutableMapOf<IrFunction, IrDeclaration?>()
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
|
inlineLambdaToCallSite.putAll(InlineReferenceLocator.scan(context, irFile).lambdaToCallSite)
|
||||||
irFile.transformChildrenVoid(this)
|
irFile.transformChildrenVoid(this)
|
||||||
pendingTransformations.forEach { it() }
|
pendingTransformations.forEach { it() }
|
||||||
}
|
}
|
||||||
@@ -437,6 +439,16 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
this == JavaVisibilities.PROTECTED_AND_PACKAGE ||
|
this == JavaVisibilities.PROTECTED_AND_PACKAGE ||
|
||||||
this == JavaVisibilities.PROTECTED_STATIC_VISIBILITY
|
this == JavaVisibilities.PROTECTED_STATIC_VISIBILITY
|
||||||
|
|
||||||
|
private fun IrDeclaration.getAccessContext(withSuper: Boolean): IrDeclarationContainer? = when {
|
||||||
|
this is IrDeclarationContainer -> this
|
||||||
|
// Accesses from public inline functions can actually be anywhere. For protected inline functions
|
||||||
|
// calling methods on `super` we need an accessor to satisfy INVOKESPECIAL constraints.
|
||||||
|
this is IrFunction && isInline && !visibility.isPrivate && (withSuper || !visibility.isProtected) -> null
|
||||||
|
// For inline lambdas, we can navigate to the only call site directly.
|
||||||
|
this in inlineLambdaToCallSite -> inlineLambdaToCallSite[this]?.getAccessContext(withSuper)
|
||||||
|
else -> (parent as? IrDeclaration)?.getAccessContext(withSuper)
|
||||||
|
}
|
||||||
|
|
||||||
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.
|
||||||
@@ -454,15 +466,9 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
|
|
||||||
// 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
|
||||||
|
val contextDeclarationContainer = (currentScope!!.irElement as IrDeclaration).getAccessContext(withSuper) ?: return false
|
||||||
|
|
||||||
// Within inline functions, we have to assume the worst.
|
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer.getPackageFragment()?.fqName
|
||||||
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 {
|
return when {
|
||||||
declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false
|
declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false
|
||||||
declaration.visibility.isProtected && !samePackage &&
|
declaration.visibility.isProtected && !samePackage &&
|
||||||
|
|||||||
Reference in New Issue
Block a user