diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 6c2fcf9d4db..40ad1c3ee49 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -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 - 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) } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 3499845db49..9fafdfecdd4 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -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, + 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 = + ArrayList(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, context: JsGenerationContext, transformer: IrElementToJsExpressionTransformer, ): List { 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)) }