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.IrElementTransformerVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.IrElementVisitorVoidWithContext
|
||||
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
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.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
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.util.*
|
||||
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.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
internal val callableReferencePhase = makeIrFilePhase(
|
||||
::CallableReferenceLowering,
|
||||
@@ -44,18 +46,19 @@ internal val callableReferencePhase = makeIrFilePhase(
|
||||
description = "Handle callable references"
|
||||
)
|
||||
|
||||
// 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 IrStatementOrigin?.isLambda
|
||||
get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION
|
||||
|
||||
private val IrFunctionReference.isIgnored: Boolean
|
||||
get() = !type.isFunctionOrKFunction() || ignoredFunctionReferences.contains(this)
|
||||
internal class InlineReferenceLocator(private val context: JvmBackendContext) : IrElementVisitorVoidWithContext() {
|
||||
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 visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
|
||||
override fun visitElement(element: IrElement) = element.acceptChildrenVoid(this)
|
||||
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
|
||||
val function = expression.symbol.owner
|
||||
if (function.isInlineFunctionCall(context)) {
|
||||
for (parameter in function.valueParameters) {
|
||||
@@ -66,16 +69,41 @@ internal class CallableReferenceLowering(private val context: JvmBackendContext)
|
||||
if (!isInlineIrExpression(valueArgument))
|
||||
continue
|
||||
|
||||
if (valueArgument is IrFunctionReference) {
|
||||
ignoredFunctionReferences.add(valueArgument)
|
||||
} else if (valueArgument is IrBlock) {
|
||||
ignoredFunctionReferences.addIfNotNull(valueArgument.statements.filterIsInstance<IrFunctionReference>().singleOrNull())
|
||||
val reference = when (valueArgument) {
|
||||
is IrFunctionReference -> valueArgument
|
||||
is IrBlock -> 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!expression.origin.isLambda)
|
||||
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
|
||||
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 val pendingTransformations = mutableListOf<Function0<Unit>>()
|
||||
private val inlineLambdaToCallSite = mutableMapOf<IrFunction, IrDeclaration?>()
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
inlineLambdaToCallSite.putAll(InlineReferenceLocator.scan(context, irFile).lambdaToCallSite)
|
||||
irFile.transformChildrenVoid(this)
|
||||
pendingTransformations.forEach { it() }
|
||||
}
|
||||
@@ -437,6 +439,16 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
||||
this == JavaVisibilities.PROTECTED_AND_PACKAGE ||
|
||||
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 {
|
||||
/// 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.
|
||||
@@ -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.
|
||||
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 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
|
||||
val samePackage = declaration.getPackageFragment()?.fqName == contextDeclarationContainer.getPackageFragment()?.fqName
|
||||
return when {
|
||||
declaration.visibility.isPrivate && symbolDeclarationContainer != contextDeclarationContainer -> false
|
||||
declaration.visibility.isProtected && !samePackage &&
|
||||
|
||||
Reference in New Issue
Block a user