JVM_IR: support vararg & defaults in function references

This commit is contained in:
pyos
2019-06-13 12:04:01 +02:00
committed by Georgy Bronnikov
parent b45e8c7021
commit 8cca74c932
16 changed files with 177 additions and 133 deletions
@@ -41,14 +41,11 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.org.objectweb.asm.Type
//Hack implementation to support CR java types in lower
@@ -167,10 +164,18 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
val irFunctionReference: IrFunctionReference
) {
private val isLambda = irFunctionReference.origin == IrStatementOrigin.LAMBDA
private val functionReferenceOrLambda = if (isLambda) context.ir.symbols.lambdaClass else context.ir.symbols.functionReference
private val callee = irFunctionReference.symbol.owner
private val calleeParameters = callee.explicitParameters
private val boundCalleeParameters = irFunctionReference.getArgumentsWithIr().map { it.first }
private val unboundCalleeParameters = calleeParameters - boundCalleeParameters
// The type of the reference is KFunction<in A1, ..., in An, out R>
private val argumentTypes = (irFunctionReference.type as IrSimpleType).arguments.dropLast(1).map { (it as IrTypeProjection).type }
private val returnType = ((irFunctionReference.type as IrSimpleType).arguments.last() as IrTypeProjection).type
private val useVararg = (argumentTypes.size >= FunctionInvokeDescriptor.Factory.BIG_ARITY)
private val typeParameters = if (callee is IrConstructor)
callee.parentAsClass.typeParameters + callee.typeParameters
@@ -180,74 +185,44 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
typeParam.symbol to irFunctionReference.getTypeArgument(typeParam.index)!!
}
private lateinit var functionReferenceClass: IrClass
private lateinit var argumentToFieldMap: Map<IrValueParameter, IrField>
private val functionReferenceClass = buildClass {
setSourceRange(irFunctionReference)
origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
name = "${callee.name.safeName()}\$${functionReferenceCount++}".synthesizedName
}.apply {
parent = referenceParent
superTypes += functionReferenceOrLambda.owner.defaultType
createImplicitParameterDeclarationWithWrappedDescriptor()
}
private val isLambda = irFunctionReference.origin == IrStatementOrigin.LAMBDA
private val functionReferenceOrLambda = if (isLambda) context.ir.symbols.lambdaClass else context.ir.symbols.functionReference
var useVararg: Boolean = false
private val argumentToFieldMap = boundCalleeParameters.associate {
it to buildField(it.name.safeName(), it.type)
}
fun build(): BuiltFunctionReference {
val returnType = irFunctionReference.symbol.owner.returnType
val functionReferenceClassSuperTypes: MutableList<IrType> = mutableListOf(functionReferenceOrLambda.owner.defaultType)
val numberOfParameters = unboundCalleeParameters.size
useVararg = (numberOfParameters >= FunctionInvokeDescriptor.Factory.BIG_ARITY)
val functionClassSymbol = if (useVararg)
val actualFunctionClass = if (useVararg)
context.ir.symbols.functionN
else
context.ir.symbols.getJvmFunctionClass(numberOfParameters)
val functionParameterTypes = unboundCalleeParameters.map { it.type }
val functionClassTypeParameters = if (useVararg)
listOf(returnType)
else
functionParameterTypes + returnType
functionReferenceClassSuperTypes += IrSimpleTypeImpl(
functionClassSymbol,
hasQuestionMark = false,
arguments = functionClassTypeParameters.map { makeTypeProjection(it, Variance.INVARIANT) },
annotations = emptyList()
)
context.ir.symbols.getJvmFunctionClass(argumentTypes.size)
functionReferenceClass.superTypes +=
actualFunctionClass.typeWith(if (useVararg) listOf(returnType) else argumentTypes + returnType)
var suspendFunctionClass: IrClass? = null
val lastParameterType = unboundCalleeParameters.lastOrNull()?.type
val lastParameterType = (calleeParameters - boundCalleeParameters).lastOrNull()?.type
if (lastParameterType is IrSimpleType &&
lastParameterType.classOrNull?.owner?.fqNameWhenAvailable?.asString() == "kotlin.coroutines.experimental.Continuation"
) {
// If the last parameter is Continuation<> inherit from SuspendFunction.
suspendFunctionClass = context.getTopLevelClass(FqName("kotlin.coroutines.SuspendFunction${numberOfParameters - 1}")).owner
val suspendFunctionClassTypeParameters = functionParameterTypes.dropLast(1) +
(lastParameterType.arguments.single() as IrTypeProjection).type
functionReferenceClassSuperTypes += IrSimpleTypeImpl(
suspendFunctionClass.symbol,
hasQuestionMark = false,
arguments = suspendFunctionClassTypeParameters.map { makeTypeProjection(it, Variance.INVARIANT) },
annotations = emptyList()
)
}
functionReferenceClass = buildClass {
setSourceRange(irFunctionReference)
origin = JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
name = "${callee.name.safeName()}\$${functionReferenceCount++}".synthesizedName
}.apply {
parent = referenceParent
superTypes.addAll(functionReferenceClassSuperTypes)
createImplicitParameterDeclarationWithWrappedDescriptor()
}
argumentToFieldMap = boundCalleeParameters.associate {
it to buildField(it.name.safeName(), it.type)
suspendFunctionClass = context.getTopLevelClass(FqName("kotlin.coroutines.SuspendFunction${argumentTypes.size - 1}")).owner
val continuationType = (lastParameterType.arguments.single() as IrTypeProjection).type
functionReferenceClass.superTypes += suspendFunctionClass.typeWith(argumentTypes + continuationType)
}
val constructor = createConstructor()
createInvokeMethod(functionClassSymbol.owner.functions.find { it.name.asString() == "invoke" }!!)
createInvokeMethod(actualFunctionClass.owner.functions.find { it.name.asString() == "invoke" }!!)
if (!isLambda) {
createGetSignatureMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getSignature"}!!)
createGetSignatureMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getSignature" }!!)
createGetNameMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getName" }!!)
createGetOwnerMethod(functionReferenceOrLambda.owner.functions.find { it.name.asString() == "getOwner" }!!)
if (suspendFunctionClass != null) {
@@ -274,25 +249,21 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
)
}
val kFunctionRefConstructorSymbol =
functionReferenceOrLambda.constructors.filter { it.owner.valueParameters.size == if (isLambda) 1 else 2 }.single()
val kFunctionRefConstructor =
functionReferenceOrLambda.owner.constructors.single { it.valueParameters.size == if (isLambda) 1 else 2 }
// The syntax (object::method) only allows to bind one of them.
val hasReceiver = irFunctionReference.dispatchReceiver != null || irFunctionReference.extensionReceiver != null
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
+irDelegatingConstructorCall(kFunctionRefConstructorSymbol.owner).apply {
putValueArgument(0, irInt(unboundCalleeParameters.size))
+irDelegatingConstructorCall(kFunctionRefConstructor).apply {
putValueArgument(0, irInt(argumentTypes.size))
if (!isLambda) {
val irReceiver = valueParameters.firstOrNull()
val receiver = boundCalleeParameters.singleOrNull()
//TODO pass proper receiver
val receiverValue = receiver?.let {
irGet(irReceiver!!.symbol.owner)
} ?: irNull()
putValueArgument(1, receiverValue)
putValueArgument(1, if (hasReceiver) irGet(valueParameters[0]) else irNull())
}
}
// Save all arguments to fields.
//TODO don't write receiver again: use it from base class
// TODO don't write receiver again: use it from base class
boundCalleeParameters.forEachIndexed { index, it ->
+irSetField(
irGet(functionReferenceClass.thisReceiver!!),
@@ -308,21 +279,15 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
buildOverride(superFunction, callee.returnType).apply {
annotations.addAll(callee.annotations)
val unboundArgsSet = unboundCalleeParameters.toSet()
if (useVararg) {
valueParameters.add(superFunction.valueParameters[0].copyTo(this))
} else {
for (param in unboundCalleeParameters) {
valueParameters += param.copyTo(
this,
index = valueParameters.size,
type = param.type.substitute(typeArgumentsMap)
)
for ((parameter, type) in superFunction.valueParameters.zip(argumentTypes)) {
valueParameters += parameter.copyTo(this, type = type)
}
}
val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset)
body = irBuilder.irBlockBody(startOffset, endOffset) {
body = context.createIrBuilder(symbol).irBlockBody(startOffset, endOffset) {
if (useVararg) {
val varargParam = valueParameters.single()
+irIfThen(
@@ -330,61 +295,71 @@ internal class CallableReferenceLowering(val context: JvmBackendContext) : FileL
irCall(arraySizeProperty.getter!!).apply {
dispatchReceiver = irGet(varargParam)
},
irInt(unboundCalleeParameters.size)
irInt(argumentTypes.size)
),
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
putValueArgument(0, irString("Expected ${unboundCalleeParameters.size} arguments"))
putValueArgument(0, irString("Expected ${argumentTypes.size} arguments"))
}
)
}
+irReturn(
irCall(irFunctionReference.symbol).apply {
for ((typeParameter, typeArgument) in typeArgumentsMap) {
putTypeArgument(typeParameter.owner.index, typeArgument)
}
var unboundIndex = 0
calleeParameters.forEach { parameter ->
val argument = when {
!unboundArgsSet.contains(parameter) ->
// Bound parameter - read from field.
irGetField(irGet(dispatchReceiverParameter!!), argumentToFieldMap[parameter]!!)
isSuspend && unboundIndex == valueParameters.size ->
// For suspend functions the last argument is continuation and it is implicit.
// irCall(getContinuationSymbol, listOf(ourSymbol.descriptor.returnType!!))
TODO()
useVararg -> {
val type = parameter.type
val varargParam = valueParameters.single()
irBlock(resultType = type) {
val argValue = irTemporary(
irCall(arrayGetFun).apply {
dispatchReceiver = irGet(varargParam)
putValueArgument(0, irInt(unboundIndex++))
}
)
+irIfThen(
irNotIs(irGet(argValue), type),
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
putValueArgument(0, irString("Wrong type, expected $type"))
}
)
+irGet(argValue)
}
}
else ->
irGet(valueParameters[unboundIndex++])
var unboundIndex = 0
fun consumeNextArgument() = if (useVararg) {
val type = argumentTypes[unboundIndex]
irBlock(resultType = type) {
val argArray = irGet(valueParameters.single())
val argIndex = irInt(unboundIndex++)
val argValue = irTemporary(irCallOp(arrayGetFun.symbol, context.irBuiltIns.anyNType, argArray, argIndex))
+irIfThen(
irNotIs(irGet(argValue), type),
irCall(context.irBuiltIns.illegalArgumentExceptionSymbol).apply {
putValueArgument(0, irString("Wrong type, expected $type"))
}
putArgument(callee, parameter, argument)
}
)
+irImplicitCast(irGet(argValue), type)
}
)
}
} else {
irGet(valueParameters[unboundIndex++])
}
val delegation = irCall(irFunctionReference.symbol).apply {
for ((typeParameter, typeArgument) in typeArgumentsMap) {
putTypeArgument(typeParameter.owner.index, typeArgument)
}
for (parameter in calleeParameters) {
when {
argumentToFieldMap.contains(parameter) ->
// Bound parameter - read from field.
irGetField(irGet(dispatchReceiverParameter!!), argumentToFieldMap[parameter]!!)
unboundIndex >= argumentTypes.size ->
// Unbound, but out of range - empty vararg or default value.
// TODO For suspend functions the last argument is continuation and it is implicit:
// irCall(getContinuationSymbol, listOf(ourSymbol.descriptor.returnType!!))
null
// If a vararg parameter corresponds to exactly one KFunction argument, which is an array, that array
// is forwarded as a spread. In all other cases, excess arguments are packed into a new array.
//
// fun f(x: (Int, Array<String>) -> String) = x(0, arrayOf("OK", "FAIL"))
// fun g(x: (Int, String, String) -> String) = x(0, "OK", "FAIL")
// fun h(i: Int, vararg xs: String) = xs[i]
// f(::h) == g(::h)
//
parameter.isVararg && (unboundIndex < argumentTypes.size - 1 || argumentTypes.last() != parameter.type) ->
IrVarargImpl(
startOffset, endOffset, parameter.type, parameter.varargElementType!!,
(unboundIndex until argumentTypes.size).map { consumeNextArgument() }
)
else ->
consumeNextArgument()
}?.let { putArgument(callee, parameter, it) }
}
}
+irReturn(delegation)
}
}
private fun buildField(fieldName: Name, fieldType: IrType): IrField =