[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.backend.common.ir.isElseBranch
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.utils.*
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.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
@@ -156,14 +153,33 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
// Argument value constructs unboxed inline class instance // Argument value constructs unboxed inline class instance
arguments.single() arguments.single()
} else { } else {
val ref = when { when {
klass.isEffectivelyExternal() -> klass.isEffectivelyExternal() -> {
context.getRefForExternalClass(klass) val refForExternalClass = context.getRefForExternalClass(klass)
val varargParameterIndex = expression.symbol.owner.varargParameterIndex()
else -> if (varargParameterIndex == -1) {
context.getNameForClass(klass).makeRef() 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.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
@@ -120,7 +121,7 @@ fun translateCall(
return JsInvocation(callRef, jsDispatchReceiver?.let { receiver -> listOf(receiver) + arguments } ?: arguments) 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 isExternalVararg = function.isEffectivelyExternal() && varargParameterIndex != -1
val symbolName = when (jsDispatchReceiver) { val symbolName = when (jsDispatchReceiver) {
@@ -134,28 +135,12 @@ fun translateCall(
} }
return if (isExternalVararg) { 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) // 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( val argumentsAsSingleArray = argumentsAsSingleArray(
arrayConcat, jsExtensionReceiver,
listOfNotNull(jsExtensionReceiver) + arguments.mapIndexed { index, argument -> arguments,
when (index) { varargParameterIndex
// 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))
}
}
) )
if (jsDispatchReceiver != null) { 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( fun translateCallArguments(
expression: IrMemberAccessExpression<*>, expression: IrMemberAccessExpression<IrFunctionSymbol>,
context: JsGenerationContext, context: JsGenerationContext,
transformer: IrElementToJsExpressionTransformer, transformer: IrElementToJsExpressionTransformer,
): List<JsExpression> { ): List<JsExpression> {
val size = expression.valueArgumentsCount val size = expression.valueArgumentsCount
val varargParameterIndex = expression.symbol.owner.realOverrideTarget.varargParameterIndex()
val validWithNullArgs = expression.validWithNullArgs() val validWithNullArgs = expression.validWithNullArgs()
val arguments = (0 until size) val arguments = (0 until size)
.mapTo(ArrayList(size)) { index -> .mapTo(ArrayList(size)) { index ->
@@ -216,6 +248,16 @@ fun translateCallArguments(
assert(validWithNullArgs) assert(validWithNullArgs)
} }
} }
.mapIndexed { index, result ->
val isEmptyExternalVararg = validWithNullArgs &&
varargParameterIndex == index &&
result is JsArrayLiteral &&
result.expressions.isEmpty()
if (isEmptyExternalVararg) {
null
} else result
}
.dropLastWhile { it == null } .dropLastWhile { it == null }
.map { it ?: JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1)) } .map { it ?: JsPrefixOperation(JsUnaryOperator.VOID, JsIntLiteral(1)) }