Generate accessors for super calls if necessary

Current implementation of calls with super qualifier relies on
invokespecial, which has some more constraints than regular virtual
invocations. When those constraints aren't met, accessors are needed.
This commit is contained in:
Ting-Yuan Huang
2019-04-09 10:49:57 -07:00
committed by max-kammerer
parent c90e6c1f18
commit 1c3b895fc0
26 changed files with 73 additions and 51 deletions
@@ -213,30 +213,41 @@ private fun IrTypeParameter.copySuperTypesFrom(source: IrTypeParameter) {
} }
} }
// Copy value parameters, dispatch receiver, and extension receiver from source to value parameters of this function.
// Type of dispatch receiver defaults to source's dispatch receiver. It is overridable in case the new function and the old one are used in
// different contexts and expect different type of dispatch receivers. The overriding type should be assign compatible to the old type.
fun IrFunction.copyValueParametersToStatic( fun IrFunction.copyValueParametersToStatic(
source: IrFunction, source: IrFunction,
origin: IrDeclarationOrigin origin: IrDeclarationOrigin,
dispatchReceiverType: IrType? = source.dispatchReceiverParameter?.type
) { ) {
val target = this val target = this
assert(target.valueParameters.isEmpty()) assert(target.valueParameters.isEmpty())
var shift = 0 var shift = 0
source.dispatchReceiverParameter?.let { p -> source.dispatchReceiverParameter?.let { originalDispatchReceiver ->
assert(dispatchReceiverType!!.isSubtypeOfClass(originalDispatchReceiver.type.classOrNull!!))
val type = dispatchReceiverType.remapTypeParameters(
(originalDispatchReceiver.parent as IrTypeParametersContainer).classIfConstructor,
target.classIfConstructor
)
target.valueParameters.add( target.valueParameters.add(
p.copyTo( originalDispatchReceiver.copyTo(
target, target,
origin = p.origin, origin = originalDispatchReceiver.origin,
index = p.index + shift++, index = originalDispatchReceiver.index + shift++,
type = type,
name = Name.identifier("\$this") name = Name.identifier("\$this")
) )
) )
} }
source.extensionReceiverParameter?.let { p -> source.extensionReceiverParameter?.let { originalExtensionReceiver ->
target.valueParameters.add( target.valueParameters.add(
p.copyTo( originalExtensionReceiver.copyTo(
target, target,
origin = p.origin, origin = originalExtensionReceiver.origin,
index = p.index + shift++, index = originalExtensionReceiver.index + shift++,
name = Name.identifier("\$receiver") name = Name.identifier("\$receiver")
) )
) )
@@ -29,7 +29,9 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.* 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.impl.IrSimpleTypeImpl 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.util.*
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid
@@ -62,8 +64,18 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
if (expression.usesDefaultArguments()) { if (expression.usesDefaultArguments()) {
return super.visitFunctionAccess(expression) return super.visitFunctionAccess(expression)
} }
fun makeFunctionAccessorSymbolWithSuper(functionSymbol: IrFunctionSymbol): IrFunctionSymbol =
makeFunctionAccessorSymbol(functionSymbol, (expression as? IrCall)?.superQualifierSymbol)
return super.visitExpression( return super.visitExpression(
handleAccess(expression, expression.symbol, functionMap, ::makeFunctionAccessorSymbol, ::modifyFunctionAccessExpression) handleAccess(
expression,
expression.symbol,
functionMap,
::makeFunctionAccessorSymbolWithSuper,
::modifyFunctionAccessExpression,
(expression as? IrCall)?.superQualifierSymbol,
(expression as? IrCall)?.dispatchReceiver?.type?.classifierOrNull as? IrClassSymbol
)
) )
} }
@@ -80,20 +92,23 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
symbol: FromSyT, symbol: FromSyT,
accumMap: MutableMap<FromSyT, ToSyT>, accumMap: MutableMap<FromSyT, ToSyT>,
symbolConverter: (FromSyT) -> ToSyT, symbolConverter: (FromSyT) -> ToSyT,
exprConverter: (ExprT, ToSyT) -> IrDeclarationReference exprConverter: (ExprT, ToSyT) -> IrDeclarationReference,
superQualifierSymbol: IrClassSymbol? = null,
thisSymbol: IrClassSymbol? = null
): IrExpression = ): IrExpression =
if (!symbol.isAccessible()) { if (!symbol.isAccessible(superQualifierSymbol != null, thisSymbol)) {
val accessorSymbol = accumMap.getOrPut(symbol) { symbolConverter(symbol) } val accessorSymbol = accumMap.getOrPut(symbol) { symbolConverter(symbol) }
exprConverter(expression, accessorSymbol) exprConverter(expression, accessorSymbol)
} else { } else {
expression expression
} }
private fun makeFunctionAccessorSymbol(functionSymbol: IrFunctionSymbol): IrFunctionSymbol = when (functionSymbol) { private fun makeFunctionAccessorSymbol(functionSymbol: IrFunctionSymbol, superQualifierSymbol: IrClassSymbol?): IrFunctionSymbol =
is IrConstructorSymbol -> functionSymbol.owner.makeConstructorAccessor().symbol when (functionSymbol) {
is IrSimpleFunctionSymbol -> functionSymbol.owner.makeSimpleFunctionAccessor().symbol is IrConstructorSymbol -> functionSymbol.owner.makeConstructorAccessor().symbol
else -> error("Unknown subclass of IrFunctionSymbol") is IrSimpleFunctionSymbol -> functionSymbol.owner.makeSimpleFunctionAccessor(superQualifierSymbol).symbol
} else -> error("Unknown subclass of IrFunctionSymbol")
}
private fun IrConstructor.makeConstructorAccessor(): IrConstructor { private fun IrConstructor.makeConstructorAccessor(): IrConstructor {
val source = this val source = this
@@ -132,7 +147,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
copyAllParamsToArgs(it, accessor) copyAllParamsToArgs(it, accessor)
} }
private fun IrSimpleFunction.makeSimpleFunctionAccessor(): IrSimpleFunction { private fun IrSimpleFunction.makeSimpleFunctionAccessor(superQualifierSymbol: IrClassSymbol?): IrSimpleFunction {
val source = this val source = this
return buildFun { return buildFun {
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
@@ -140,26 +155,41 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
visibility = Visibilities.PUBLIC visibility = Visibilities.PUBLIC
isSuspend = source.isSuspend isSuspend = source.isSuspend
}.also { accessor -> }.also { accessor ->
accessor.parent = source.parent accessor.parent = if ((source.parent as? IrClass)?.isInterface == true) {
// Accessors for interfaces are only for private methods. They should always be placed in DefaultImpls. The only exception
// is private methods annotated with @JvmDefault; Only in such cases JVM default method is generated.
// 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
}
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) } pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
accessor.copyTypeParametersFrom(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR) accessor.copyTypeParametersFrom(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
accessor.copyValueParametersToStatic(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR) // When a method is defined in class C1 but called on C1's subclass C2, source.dispatchReceiverParameter.type can be resolved
// to C1, while the method symbol still bound to C2, which expects a receiver of type C2. Therefore, we need to specify
// dispatchReceiver's type using super qualifier's type explicitly.
accessor.copyValueParametersToStatic(
source,
JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR,
superQualifierSymbol?.owner?.defaultType ?: source.dispatchReceiverParameter?.type
)
accessor.returnType = source.returnType.remapTypeParameters(source, accessor) accessor.returnType = source.returnType.remapTypeParameters(source, accessor)
accessor.body = IrExpressionBodyImpl( accessor.body = IrExpressionBodyImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
createSimpleFunctionCall(accessor, source.symbol) createSimpleFunctionCall(accessor, source.symbol, superQualifierSymbol)
) )
} }
} }
private fun createSimpleFunctionCall(accessor: IrFunction, targetSymbol: IrFunctionSymbol) = private fun createSimpleFunctionCall(accessor: IrFunction, targetSymbol: IrFunctionSymbol, superQualifierSymbol: IrClassSymbol?) =
IrCallImpl( IrCallImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET, UNDEFINED_OFFSET,
accessor.returnType, accessor.returnType,
targetSymbol, targetSymbol.descriptor, targetSymbol, targetSymbol.descriptor,
targetSymbol.owner.typeParameters.size targetSymbol.owner.typeParameters.size,
superQualifierSymbol = superQualifierSymbol
).also { ).also {
copyAllParamsToArgs(it, accessor) copyAllParamsToArgs(it, accessor)
} }
@@ -253,8 +283,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
oldExpression.type, oldExpression.type,
accessorSymbol, accessorSymbol.descriptor, accessorSymbol, accessorSymbol.descriptor,
oldExpression.typeArgumentsCount, oldExpression.typeArgumentsCount,
oldExpression.origin, oldExpression.origin
oldExpression.superQualifierSymbol
) )
is IrDelegatingConstructorCall -> IrDelegatingConstructorCallImpl( is IrDelegatingConstructorCall -> IrDelegatingConstructorCallImpl(
oldExpression.startOffset, oldExpression.endOffset, oldExpression.startOffset, oldExpression.endOffset,
@@ -377,7 +406,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
return Name.identifier("access\$prop\$$setterName") return Name.identifier("access\$prop\$$setterName")
} }
private fun IrSymbol.isAccessible(): 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.
@@ -390,7 +419,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
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. // The only two visibilities where Kotlin rules differ from JVM rules.
if (!Visibilities.isPrivate(declaration.visibility) && declaration.visibility != Visibilities.PROTECTED) return true if (!withSuper && !Visibilities.isPrivate(declaration.visibility) && declaration.visibility != Visibilities.PROTECTED) 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
@@ -407,6 +436,12 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
(declaration.visibility == Visibilities.PROTECTED && !samePackage && (declaration.visibility == Visibilities.PROTECTED && !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
// 1. `this` to be assign compatible with current class.
// 2. the method is a member of a superclass of current class.
(withSuper && contextDeclarationContainer is IrClass && symbolDeclarationContainer is IrClass &&
((thisObjReference != null && !contextDeclarationContainer.symbol.isSubtypeOfClass(thisObjReference)) ||
!(contextDeclarationContainer.isSubclassOf(symbolDeclarationContainer)))) -> false
else -> true else -> true
} }
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
fun foo(): String fun foo(): String
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
fun foo(): String fun foo(): String
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
fun foo(): String fun foo(): String
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
fun foo(): String fun foo(): String
} }
@@ -1,5 +1,4 @@
// !JVM_DEFAULT_MODE: enable // !JVM_DEFAULT_MODE: enable
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// JVM_TARGET: 1.8 // JVM_TARGET: 1.8
// WITH_RUNTIME // WITH_RUNTIME
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
// JVM_TARGET: 1.8 // JVM_TARGET: 1.8
// WITH_RUNTIME // WITH_RUNTIME
@@ -1,5 +1,4 @@
// WITH_RUNTIME // WITH_RUNTIME
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM // TARGET_BACKEND: JVM
interface Z { interface Z {
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
// There must be no delegation methods for 'log' and 'bar' in C as they are private // There must be no delegation methods for 'log' and 'bar' in C as they are private
private val log: String get() = "O" private val log: String get() = "O"
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface BK { interface BK {
fun x() : Int = 50 fun x() : Int = 50
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
open class M() { open class M() {
open var y = 500 open var y = 500
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
//inspired by kt3492 //inspired by kt3492
interface BK { interface BK {
fun foo(): String fun foo(): String
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface Base { interface Base {
val foo: String val foo: String
fun bar(): String fun bar(): String
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
//inspired by kt3492 //inspired by kt3492
interface Base { interface Base {
val foo: String val foo: String
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface T { interface T {
open fun baz(): String = "T.baz" open fun baz(): String = "T.baz"
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface T { interface T {
open val baz: String open val baz: String
get() = "T.baz" get() = "T.baz"
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
open class A { open class A {
open fun foo2(): String = "OK" open fun foo2(): String = "OK"
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
open class A { open class A {
open val foo: String = "OK" open val foo: String = "OK"
} }
-1
View File
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface ATrait { interface ATrait {
open fun foo2(): String = "OK" open fun foo2(): String = "OK"
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
open val foo: String open val foo: String
get() = "OK" get() = "OK"
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS // IGNORE_BACKEND: JS
// IGNORE_BACKEND: JVM_IR
interface A { interface A {
private fun foo() = "OK" private fun foo() = "OK"
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
var result = "fail" var result = "fail"
interface B { interface B {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
open class B { open class B {
val p = "OK" val p = "OK"
} }
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface Z { interface Z {
fun testFun() : String { fun testFun() : String {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
interface Z { interface Z {
fun testFun(): String { fun testFun(): String {