JS: support callable references with vararg and default parameters conversion

This commit is contained in:
Anton Bannykh
2020-02-20 15:22:01 +03:00
parent f6a23ea441
commit e7816b4ec2
24 changed files with 152 additions and 41 deletions
@@ -2213,6 +2213,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/simpleEmptyVararg.kt");
}
@TestMetadata("unboundReferences.kt")
public void testUnboundReferences() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/unboundReferences.kt");
}
@TestMetadata("varargViewedAsArray.kt")
public void testVarargViewedAsArray() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/varargViewedAsArray.kt");
@@ -2213,6 +2213,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/simpleEmptyVararg.kt");
}
@TestMetadata("unboundReferences.kt")
public void testUnboundReferences() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/unboundReferences.kt");
}
@TestMetadata("varargViewedAsArray.kt")
public void testVarargViewedAsArray() throws Exception {
runTest("compiler/testData/codegen/box/callableReference/varargAndDefaults/varargViewedAsArray.kt");
@@ -233,7 +233,7 @@ class CallArgumentTranslator private constructor(
val arguments = resolvedArgument.arguments
if (arguments.isEmpty()) {
return if (shouldWrapVarargInArray) {
return toArray(varargElementType, listOf()).wrapInUArray(varargElementType)
return toArray(varargElementType, mutableListOf()).wrapInUArray(varargElementType)
} else {
null
}
@@ -28,13 +28,12 @@ import org.jetbrains.kotlin.js.translate.general.Translation
import org.jetbrains.kotlin.js.translate.utils.*
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getFunctionResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.calls.callUtil.getPropertyResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.calls.model.DelegatingResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
@@ -74,18 +73,57 @@ object CallableReferenceTranslator {
}
private fun translateForFunction(
descriptor: FunctionDescriptor,
context: TranslationContext,
expression: KtCallableReferenceExpression,
receiver: JsExpression?
descriptor: FunctionDescriptor,
context: TranslationContext,
expression: KtCallableReferenceExpression,
receiver: JsExpression?
): JsExpression {
val realResolvedCall = expression.callableReference.getFunctionResolvedCallWithAssert(context.bindingContext())
val fakeExpression = CodegenUtil.constructFakeFunctionCall(expression.project, descriptor.valueParameters.size)
val functionDescriptor = context.bindingContext().get(BindingContext.FUNCTION, expression)!!
val fakeCall = CallMaker.makeCall(fakeExpression, null, null, fakeExpression, fakeExpression.valueArguments)
val receivers =
if (receiver == null && (descriptor.dispatchReceiverParameter != null || descriptor.extensionReceiverParameter != null)) 1 else 0
val fakeArgCount = functionDescriptor.valueParameters.size - receivers
val fakeExpression = CodegenUtil.constructFakeFunctionCall(expression.project, fakeArgCount)
val fakeArguments = fakeExpression.valueArguments
val fakeCall = CallMaker.makeCall(fakeExpression, null, null, fakeExpression, fakeArguments)
val fakeResolvedCall = object : DelegatingResolvedCall<FunctionDescriptor>(realResolvedCall) {
val valueArgumentList = fakeCall.valueArguments.map(::ExpressionValueArgument)
val valueArgumentMap = valueArgumentList.withIndex().associate { (index, arg) -> descriptor.valueParameters[index] to arg }
val valueArgumentMap = mutableMapOf<ValueParameterDescriptor, ResolvedValueArgument>().also { argumentMap ->
var i = 0
for (parameter in descriptor.valueParameters) {
if (parameter.varargElementType != null) {
// Two cases are possible for a function reference with a vararg parameter of type T: either several arguments
// of type T are bound to that parameter, or one argument of type Array<out T>. In the former case the argument
// is bound as a VarargValueArgument, in the latter it's an ExpressionValueArgument
if (i == fakeArgCount) {
// If we've exhausted the argument list of the reference and we still have one vararg parameter left,
// we should use its default value if present, or simply an empty vararg instead
argumentMap[parameter] =
if (parameter.hasDefaultValue()) DefaultValueArgument.DEFAULT else VarargValueArgument()
continue
}
if (functionDescriptor.valueParameters[receivers + i].type == parameter.varargElementType) {
argumentMap[parameter] = VarargValueArgument(fakeArguments.subList(i, fakeArgCount))
i = fakeArgCount
continue
}
}
if (i < fakeArgCount) {
argumentMap[parameter] = ExpressionValueArgument(fakeArguments.get(i++))
} else {
assert(parameter.hasDefaultValue()) {
"Parameter should be either vararg or expression or default: " + parameter +
" (reference in: " + functionDescriptor.containingDeclaration + ")"
}
argumentMap[parameter] = DefaultValueArgument.DEFAULT
}
}
}
val valueArgumentList = valueArgumentMap.values.toList()
override fun getCall() = fakeCall
@@ -96,8 +134,7 @@ object CallableReferenceTranslator {
override fun getExplicitReceiverKind(): ExplicitReceiverKind {
if (receiver != null) {
return if (descriptor.isExtension) ExplicitReceiverKind.EXTENSION_RECEIVER else ExplicitReceiverKind.DISPATCH_RECEIVER
}
else {
} else {
return super.getExplicitReceiverKind()
}
}
@@ -106,36 +143,35 @@ object CallableReferenceTranslator {
val function = JsFunction(context.scope(), JsBlock(), "")
function.source = expression
val receiverParam = if (descriptor.dispatchReceiverParameter != null ||
descriptor.extensionReceiverParameter != null ||
receiver != null) {
descriptor.extensionReceiverParameter != null ||
receiver != null
) {
val paramName = JsScope.declareTemporaryName(Namer.getReceiverParameterName())
function.parameters += JsParameter(paramName)
paramName.makeRef()
}
else {
} else {
null
}
val functionDescriptor = realResolvedCall.resultingDescriptor
val aliases = mutableMapOf<KtExpression, JsExpression>()
for ((index, valueArg) in fakeCall.valueArguments.withIndex()) {
val paramName = JsScope.declareTemporaryName(descriptor.valueParameters[index].name.asString())
val paramName = JsScope.declareTemporaryName(functionDescriptor.valueParameters[index].name.asString())
function.parameters += JsParameter(paramName)
val paramRef = paramName.makeRef()
paramRef.type = context.currentModule.builtIns.anyType
val type = functionDescriptor.valueParameters[index].type
aliases[valueArg.getArgumentExpression()!!] = TranslationUtils.coerce(context, paramRef, type)
aliases[valueArg.getArgumentExpression()!!] = paramRef
}
var functionContext = context.innerBlock(function.body).innerContextWithAliasesForExpressions(aliases).inner(functionDescriptor)
var functionContext = context.innerBlock(function.body).innerContextWithAliasesForExpressions(aliases).inner(descriptor)
functionContext.continuationParameterDescriptor?.let { continuationDescriptor ->
function.parameters += JsParameter(context.getNameForDescriptor(continuationDescriptor))
functionContext = functionContext.innerContextWithDescriptorsAliased(mapOf(continuationDescriptor to JsAstUtils.stateMachineReceiver()))
functionContext =
functionContext.innerContextWithDescriptorsAliased(mapOf(continuationDescriptor to JsAstUtils.stateMachineReceiver()))
}
if (functionDescriptor.isSuspend) {
function.fillCoroutineMetadata(functionContext, descriptor, hasController = false)
if (descriptor.isSuspend) {
function.fillCoroutineMetadata(functionContext, functionDescriptor, hasController = false)
}
val invocation = CallTranslator.translate(functionContext, fakeResolvedCall, receiverParam)
+8
View File
@@ -63,6 +63,8 @@ external fun oneMoreParamCount(before: IntArray, vararg middle: Int, after: IntA
@JsName("paramCount")
external fun <T> oneMoreGenericParamCount(before: Array<T>, vararg middle: T, after: Array<T>): Int
fun runCallable(fn: (Int, Int, Int, Int, Int) -> Int, a1: Int, a2: Int, a3: Int, a4: Int, a5: Int): Int = fn(a1, a2, a3, a4, a5)
fun box(): String {
if (paramCount() != 0)
return "failed when call native function without args"
@@ -147,5 +149,11 @@ fun box(): String {
assertEquals(6, oneMoreParamCount(intArrayOf(1, 2), 3, *intArrayOf(4, 5), 6, after = intArrayOf(7, 8)))
assertEquals(6, oneMoreGenericParamCount(arrayOf("1", "2"), "3", *arrayOf("4", "5"), "6", after = arrayOf("7", "8")))
assertEquals(5, runCallable(::paramCount, 1, 2, 3, 4, 5))
assertEquals(5, runCallable(::anotherParamCount, 1, 2, 3, 4, 5))
assertEquals(5, runCallable(::anotherCount, 1, 2, 3, 4, 5))
assertEquals(11111, runCallable(::sumOfParameters, 1, 10, 100, 1000, 10000))
return "OK"
}