JVM_IR: support vararg & defaults in function references
This commit is contained in:
+98
-123
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user