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 =
@@ -0,0 +1,21 @@
var result = "Fail"
fun foo(
a01: Int, a02: Int, a03: Int, a04: Int, a05: Int, a06: Int, a07: Int, a08: Int, a09: Int, a10: Int,
a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int,
a21: Int, a22: Int, a23: Int, a24: Int, a25: Int, a26: Int, a27: Int, a28: Int, a29: Int, a30: Int,
a31: Int, a32: Int, a33: Int, a34: Int, a35: Int, a36: Int, a37: Int, a38: Int, a39: Int, a40: Int
) {
result = "OK"
}
fun box(): String {
val x = ::foo
x(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40
)
return result
}
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
fun foo(s: String = "kotlin", vararg t: String): Boolean {
if (s != "kotlin") throw AssertionError(s)
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
// WITH_RUNTIME
import kotlin.test.assertEquals
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
fun foo(vararg a: String, result: String = "OK"): String =
if (a.size == 0) result else "Fail"
@@ -1,5 +1,4 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JVM_IR
fun foo(x: String, y: Char = 'K'): String = x + y
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
fun foo(x: String = "O", vararg y: String): String =
if (y.size == 0) x + "K" else "Fail"
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
class Outer(val o: String) {
@@ -0,0 +1,20 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS
fun foo(
f: (
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int,
Int, Int, Int, Int, Int, Int, Int, Int, Int, Int
) -> String
) = f(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40
)
fun bar(first: Int, vararg args: Int) = if (args.size == 39) "OK" else "Fail: ${args.size}"
fun box(): String = foo(::bar)
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
// WITH_RUNTIME
import kotlin.test.assertEquals
@@ -1,5 +1,4 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JVM_IR
fun foo(x: String, y: String = "K"): String = x + y
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
fun foo(x: String, vararg y: String): String =
if (y.size == 0) x + "K" else "Fail"
@@ -1,5 +1,5 @@
// !LANGUAGE: +NewInference
// IGNORE_BACKEND: JS, JVM_IR
// IGNORE_BACKEND: JS
fun foo(x: Int, s: Int, vararg y: CharSequence = arrayOf("Aaa")): String =
if (y.size == s && y[0].length == x) "OK" else "Fail"
@@ -2210,6 +2210,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelStringOneStringArg.kt");
}
@TestMetadata("topLevelFromTopLevelUnitManyArgs.kt")
public void testTopLevelFromTopLevelUnitManyArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitManyArgs.kt");
}
@TestMetadata("topLevelFromTopLevelUnitNoArgs.kt")
public void testTopLevelFromTopLevelUnitNoArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitNoArgs.kt");
@@ -2577,6 +2582,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/innerConstructorWithVararg.kt");
}
@TestMetadata("largeVararg.kt")
public void testLargeVararg() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/largeVararg.kt");
}
@TestMetadata("localFunctionWithDefault.kt")
public void testLocalFunctionWithDefault() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/localFunctionWithDefault.kt");
@@ -2210,6 +2210,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelStringOneStringArg.kt");
}
@TestMetadata("topLevelFromTopLevelUnitManyArgs.kt")
public void testTopLevelFromTopLevelUnitManyArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitManyArgs.kt");
}
@TestMetadata("topLevelFromTopLevelUnitNoArgs.kt")
public void testTopLevelFromTopLevelUnitNoArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitNoArgs.kt");
@@ -2577,6 +2582,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/innerConstructorWithVararg.kt");
}
@TestMetadata("largeVararg.kt")
public void testLargeVararg() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/largeVararg.kt");
}
@TestMetadata("localFunctionWithDefault.kt")
public void testLocalFunctionWithDefault() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/localFunctionWithDefault.kt");
@@ -2210,6 +2210,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelStringOneStringArg.kt");
}
@TestMetadata("topLevelFromTopLevelUnitManyArgs.kt")
public void testTopLevelFromTopLevelUnitManyArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitManyArgs.kt");
}
@TestMetadata("topLevelFromTopLevelUnitNoArgs.kt")
public void testTopLevelFromTopLevelUnitNoArgs() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/function/topLevelFromTopLevelUnitNoArgs.kt");
@@ -2577,6 +2582,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/innerConstructorWithVararg.kt");
}
@TestMetadata("largeVararg.kt")
public void testLargeVararg() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/largeVararg.kt");
}
@TestMetadata("localFunctionWithDefault.kt")
public void testLocalFunctionWithDefault() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/localFunctionWithDefault.kt");