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:
pyos
2019-10-02 11:21:45 +02:00
committed by max-kammerer
parent d86e87d35e
commit fa54b2cb81
2 changed files with 56 additions and 25 deletions
@@ -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
}
@@ -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 &&