[JS IR] Constructor call with vararg invoking with apply

[JS IR] Nullize external empty varargs

[JS IR] Concat varargs with array of nonVarargs arguments

^KT-42357 fixed
This commit is contained in:
Ilya Goncharov
2020-11-20 23:50:44 +03:00
parent ac42dcd8da
commit 67e4b0948e
2 changed files with 91 additions and 33 deletions
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -156,14 +153,33 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
// Argument value constructs unboxed inline class instance
arguments.single()
} else {
val ref = when {
klass.isEffectivelyExternal() ->
context.getRefForExternalClass(klass)
else ->
context.getNameForClass(klass).makeRef()
when {
klass.isEffectivelyExternal() -> {
val refForExternalClass = context.getRefForExternalClass(klass)
val varargParameterIndex = expression.symbol.owner.varargParameterIndex()
if (varargParameterIndex == -1) {
JsNew(refForExternalClass, arguments)
} else {
val argumentsAsSingleArray = argumentsAsSingleArray(
JsNullLiteral(),
arguments,
varargParameterIndex
)
JsNew(
JsInvocation(
JsNameRef("apply", JsNameRef("bind", JsNameRef("Function"))),
refForExternalClass,
argumentsAsSingleArray
),
emptyList()
)
}
}
else -> {
val ref = context.getNameForClass(klass).makeRef()
JsNew(ref, arguments)
}
}
JsNew(ref, arguments)
}
}
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -120,7 +121,7 @@ fun translateCall(
return JsInvocation(callRef, jsDispatchReceiver?.let { receiver -> listOf(receiver) + arguments } ?: arguments)
}
val varargParameterIndex = function.valueParameters.indexOfFirst { it.varargElementType != null }
val varargParameterIndex = function.varargParameterIndex()
val isExternalVararg = function.isEffectivelyExternal() && varargParameterIndex != -1
val symbolName = when (jsDispatchReceiver) {
@@ -134,28 +135,12 @@ fun translateCall(
}
return if (isExternalVararg) {
// External vararg arguments should be represented in JS as multiple "plain" arguments (opposed to arrays in Kotlin)
// We are using `Function.prototype.apply` function to pass all arguments as a single array.
// For this purpose are concatenating non-vararg arguments with vararg.
// TODO: Don't use `Function.prototype.apply` when number of arguments is known at compile time (e.g. there are no spread operators)
val arrayConcat = JsNameRef("concat", JsArrayLiteral())
val arraySliceCall = JsNameRef("call", JsNameRef("slice", JsArrayLiteral()))
val argumentsAsSingleArray = JsInvocation(
arrayConcat,
listOfNotNull(jsExtensionReceiver) + arguments.mapIndexed { index, argument ->
when (index) {
// Call `Array.prototype.slice` on vararg arguments in order to convert array-like objects into proper arrays
// TODO: Optimize for proper arrays
varargParameterIndex -> JsInvocation(arraySliceCall, argument)
// TODO: Don't wrap non-array-like arguments with array literal
// TODO: Wrap adjacent non-vararg arguments in a single array literal
else -> JsArrayLiteral(listOf(argument))
}
}
val argumentsAsSingleArray = argumentsAsSingleArray(
jsExtensionReceiver,
arguments,
varargParameterIndex
)
if (jsDispatchReceiver != null) {
@@ -198,13 +183,60 @@ fun translateCall(
}
}
internal fun argumentsAsSingleArray(
additionalReceiver: JsExpression?,
arguments: List<JsExpression>,
varargParameterIndex: Int?
): JsExpression {
// External vararg arguments should be represented in JS as multiple "plain" arguments (opposed to arrays in Kotlin)
// We are using `Function.prototype.apply` function to pass all arguments as a single array.
// For this purpose are concatenating non-vararg arguments with vararg.
var varargArgument: JsExpression? = null
val additionalSize = additionalReceiver?.let { 1 } ?: 0
// size + 1 because arguments size + potential additionalReceiver
val nonVarArgs: MutableList<JsExpression> =
ArrayList<JsExpression>(arguments.size + additionalSize).apply {
additionalReceiver?.let { add(it) }
}
arguments
.forEachIndexed { index, argument ->
when (index) {
// Call `Array.prototype.slice` on vararg arguments in order to convert array-like objects into proper arrays
varargParameterIndex -> {
varargArgument = if (argument is JsArrayLiteral) {
argument
} else {
val arraySliceCall = JsNameRef("call", JsNameRef("slice", JsArrayLiteral()))
JsInvocation(arraySliceCall, argument)
}
}
else -> nonVarArgs.add(argument)
}
}
val nonVarArgArrayLiteral = JsArrayLiteral(nonVarArgs)
return varargArgument?.let {
JsInvocation(
JsNameRef("concat", nonVarArgArrayLiteral),
it
)
} ?: nonVarArgArrayLiteral
}
fun IrFunction.varargParameterIndex() = valueParameters.indexOfFirst { it.varargElementType != null }
fun translateCallArguments(
expression: IrMemberAccessExpression<*>,
expression: IrMemberAccessExpression<IrFunctionSymbol>,
context: JsGenerationContext,
transformer: IrElementToJsExpressionTransformer,
): List<JsExpression> {
val size = expression.valueArgumentsCount
val varargParameterIndex = expression.symbol.owner.realOverrideTarget.varargParameterIndex()
val validWithNullArgs = expression.validWithNullArgs()
val arguments = (0 until size)
.mapTo(ArrayList(size)) { index ->
@@ -216,6 +248,16 @@ fun translateCallArguments(
assert(validWithNullArgs)
}
}
.mapIndexed { index, result ->
val isEmptyExternalVararg = validWithNullArgs &&
varargParameterIndex == index &&
result is JsArrayLiteral &&
result.expressions.isEmpty()
if (isEmptyExternalVararg) {
null
} else result
}
.dropLastWhile { it == null }
.map { it ?: JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1)) }