From 4a724611d86a3577582ab32285ad7d8dca646cab Mon Sep 17 00:00:00 2001 From: Sergej Jaskiewicz Date: Wed, 22 Dec 2021 20:56:19 +0300 Subject: [PATCH] [JS IR] Extract varargs in bridges generated for external methods The issue this commit fixes occurs when we have an external interface implemented by a Kotlin class, if that interface has methods with varargs. Kotlin functions expect varargs passed as arrays, but JavaScript code may be unaware of this convention. So, when generating a bridge for external interface method implementaion, we insert some additional logic for extracting varargs using the JavaScript `arguments` object. A simplified example: ```kotlin external interface Adder { fun sum(vararg numbers: Int): Int } class AdderImpl: Adder { override fun sum(vararg numbers: Int) = numbers.sum() } ``` For `AdderImpl` we generate the following JS code: ```js AdderImpl.prototype.sum_69wd7h_k$ = function (numbers) { return sum(numbers); }; AdderImpl.prototype.sum = function () { var numbers = new Int32Array([].slice.call(arguments)); return this.sum_69wd7h_k$(numbers); }; ``` #KT-15223 Fixed --- .../backend/js/lower/BridgesConstruction.kt | 83 ++++++--- .../backend/js/lower/JsBridgesConstruction.kt | 129 ++++++++++++- .../ir/backend/js/lower/VarargLowering.kt | 171 +++++++++--------- .../js/transformers/irToJs/jsAstUtils.kt | 3 + .../substitutionInSuperClass/simple.kt | 4 +- .../js/test/ir/IrBoxJsTestGenerated.java | 6 + .../box/vararg/jsExternalInterfaceVararg.kt | 159 ++++++++++++++++ 7 files changed, 439 insertions(+), 116 deletions(-) create mode 100644 js/js.translator/testData/box/vararg/jsExternalInterfaceVararg.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt index 333de693ed7..c3e673252b6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BridgesConstruction.kt @@ -13,41 +13,54 @@ import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext -import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.varargParameterIndex import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.buildFun import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.util.* -// Constructs bridges for inherited generic functions -// -// Example: for given class hierarchy -// -// class C { -// fun foo(t: T) = ... -// } -// -// class D : C { -// override fun foo(t: Int) = impl -// } -// -// it adds method D that delegates generic calls to implementation: -// -// class D : C { -// override fun foo(t: Int) = impl -// fun foo(t: Any?) = foo(t as Int) // Constructed bridge -// } -// +/** + * Constructs bridges for inherited generic functions + * + * Example: for given class hierarchy + * + * class C { + * fun foo(t: T) = ... + * } + * + * class D : C { + * override fun foo(t: Int) = impl + * } + * + * it adds method D that delegates generic calls to implementation: + * + * class D : C { + * override fun foo(t: Int) = impl + * fun foo(t: Any?) = foo(t as Int) // Constructed bridge + * } + */ abstract class BridgesConstruction(val context: T) : DeclarationTransformer { private val specialBridgeMethods = SpecialBridgeMethods(context) abstract fun getFunctionSignature(function: IrSimpleFunction): Any + /** + * Usually just returns [irFunction]'s value parameters, but special transformations may be required if, + * for example, we're dealing with an external function, and that function contains a vararg, + * which we must extract and convert to an array. + */ + protected open fun extractValueParameters( + blockBodyBuilder: IrBlockBodyBuilder, + irFunction: IrSimpleFunction, + bridge: IrSimpleFunction + ): List = irFunction.valueParameters + // Should dispatch receiver type be casted inside a bridge. open val shouldCastDispatchReceiver: Boolean = false @@ -131,7 +144,7 @@ abstract class BridgesConstruction(val context: T) : copyTypeParametersFrom(bridge) val substitutionMap = makeTypeParameterSubstitutionMap(bridge, this) copyReceiverParametersFrom(bridge, substitutionMap) - valueParameters += bridge.valueParameters.map { p -> p.copyTo(this, type = p.type.substitute(substitutionMap)) } + copyValueParametersFrom(bridge, substitutionMap) annotations += bridge.annotations overriddenSymbols += delegateTo.overriddenSymbols overriddenSymbols += bridge.symbol @@ -139,11 +152,12 @@ abstract class BridgesConstruction(val context: T) : irFunction.body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) { statements += context.createIrBuilder(irFunction.symbol).irBlockBody(irFunction) { + val valueParameters = extractValueParameters(this, irFunction, bridge) if (specialMethodInfo != null) { - irFunction.valueParameters.take(specialMethodInfo.argumentsToCheck).forEach { + valueParameters.take(specialMethodInfo.argumentsToCheck).forEachIndexed { index, valueDeclaration -> +irIfThen( context.irBuiltIns.unitType, - irNot(irIs(irGet(it), delegateTo.valueParameters[it.index].type)), + irNot(irIs(irGet(valueDeclaration), delegateTo.valueParameters[index].type)), irReturn(specialMethodInfo.defaultValueGenerator(irFunction)) ) } @@ -161,9 +175,9 @@ abstract class BridgesConstruction(val context: T) : call.extensionReceiver = irCastIfNeeded(irGet(it), delegateTo.extensionReceiverParameter!!.type) } - val toTake = irFunction.valueParameters.size - if (call.isSuspend xor irFunction.isSuspend) 1 else 0 + val toTake = valueParameters.size - if (call.isSuspend xor irFunction.isSuspend) 1 else 0 - irFunction.valueParameters.subList(0, toTake).mapIndexed { i, valueParameter -> + valueParameters.subList(0, toTake).mapIndexed { i, valueParameter -> call.putValueArgument(i, irCastIfNeeded(irGet(valueParameter), delegateTo.valueParameters[i].type)) } @@ -174,6 +188,22 @@ abstract class BridgesConstruction(val context: T) : return irFunction } + /** + * Copies the value parameters from [bridge] to [this]. If [bridge] is external and contains a vararg parameter, + * only copies the parameters before the vararg. + * The rest parameters are expected to be obtained later using the `arguments` object in JS. + */ + private fun IrSimpleFunction.copyValueParametersFrom(bridge: IrSimpleFunction, substitutionMap: Map) { + var valueParametersToCopy = bridge.valueParameters + if (bridge.isEffectivelyExternal()) { + val varargIndex = bridge.varargParameterIndex() + if (varargIndex != -1) { + valueParametersToCopy = bridge.valueParameters.take(varargIndex) + } + } + valueParameters += valueParametersToCopy.map { p -> p.copyTo(this, type = p.type.substitute(substitutionMap)) } + } + abstract fun getBridgeOrigin(bridge: IrSimpleFunction): IrDeclarationOrigin // TODO: get rid of Unit check @@ -213,6 +243,3 @@ data class IrBasedFunctionHandle(val function: IrSimpleFunction) : FunctionHandl override fun getOverridden() = function.overriddenSymbols.map { IrBasedFunctionHandle(it.owner) } } - - - diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsBridgesConstruction.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsBridgesConstruction.kt index 106c06d03af..2caf6b3a85b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsBridgesConstruction.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/JsBridgesConstruction.kt @@ -7,18 +7,34 @@ package org.jetbrains.kotlin.ir.backend.js.lower import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrArithBuilder +import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.varargParameterIndex import org.jetbrains.kotlin.ir.backend.js.utils.eraseGenerics import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName import org.jetbrains.kotlin.ir.backend.js.utils.hasStableJsName -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classifierOrNull import org.jetbrains.kotlin.ir.types.isUnit +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.name.Name class JsBridgesConstruction(context: JsIrBackendContext) : BridgesConstruction(context) { + private val calculator = JsIrArithBuilder(context) + + private val jsArguments = context.intrinsics.jsArguments + private val jsArrayGet = context.intrinsics.jsArrayGet + private val jsArrayLength = context.intrinsics.jsArrayLength + private val jsArrayLike2Array = context.intrinsics.jsArrayLike2Array + private val jsSliceArrayLikeFromIndex = context.intrinsics.jsSliceArrayLikeFromIndex + private val jsSliceArrayLikeFromIndexToIndex = context.intrinsics.jsSliceArrayLikeFromIndexToIndex + private val primitiveArrays = context.intrinsics.primitiveArrays + private val primitiveToLiteralConstructor = context.intrinsics.primitiveToLiteralConstructor + private fun IrType.getJsInlinedClass() = context.inlineClassesUtils.getInlinedClass(this) override fun getFunctionSignature(function: IrSimpleFunction): JsSignature = @@ -40,6 +56,115 @@ class JsBridgesConstruction(context: JsIrBackendContext) : BridgesConstruction { + + if (!bridge.isEffectivelyExternal()) + return super.extractValueParameters(blockBodyBuilder, irFunction, bridge) + + val varargIndex = bridge.varargParameterIndex() + + if (varargIndex == -1) + return super.extractValueParameters(blockBodyBuilder, irFunction, bridge) + + return blockBodyBuilder.run { + + // The number of parameters after the vararg + val numberOfTrailingParameters = bridge.valueParameters.size - (varargIndex + 1) + + val getTotalNumberOfArguments = irCall(jsArrayLength).apply { + putValueArgument(0, irCall(jsArguments)) + type = context.irBuiltIns.intType + } + + val firstTrailingParameterIndexVar = lazy { + irTemporary( + if (numberOfTrailingParameters == 0) + getTotalNumberOfArguments + else + calculator.sub( + getTotalNumberOfArguments, + irInt(numberOfTrailingParameters) + ), + nameHint = "firstTrailingParameterIndex" + ) + } + + val varargArrayVar = emitCopyVarargToArray( + bridge, + varargIndex, + numberOfTrailingParameters, + firstTrailingParameterIndexVar + ) + + val trailingParametersVars = createVarsForTrailingParameters(bridge, numberOfTrailingParameters, firstTrailingParameterIndexVar) + + irFunction.valueParameters + varargArrayVar + trailingParametersVars + } + } + + private fun IrBlockBodyBuilder.createVarsForTrailingParameters( + bridge: IrSimpleFunction, + numberOfTrailingParameters: Int, + firstTrailingParameterIndexVar: Lazy + ) = bridge.valueParameters.takeLast(numberOfTrailingParameters).mapIndexed { index, trailingParameter -> + val parameterIndex = if (index == 0) + irGet(firstTrailingParameterIndexVar.value) + else + calculator.add(irGet(firstTrailingParameterIndexVar.value), irInt(index)) + createTmpVariable( + irCall(jsArrayGet).apply { + putValueArgument(0, irCall(jsArguments)) + putValueArgument(1, parameterIndex) + }, + nameHint = trailingParameter.name.asString(), + irType = trailingParameter.type + ) + } + + private fun IrBlockBodyBuilder.emitCopyVarargToArray( + bridge: IrSimpleFunction, + varargIndex: Int, + numberOfTrailingParameters: Int, + firstTrailingParameterIndexVar: Lazy + ): IrVariable { + val varargElement = bridge.valueParameters[varargIndex] + val sliceIntrinsicArgs = mutableListOf(irCall(jsArguments)) + var sliceIntrinsic = jsArrayLike2Array + if (varargIndex != 0 || numberOfTrailingParameters > 0) { + sliceIntrinsicArgs.add(irInt(varargIndex)) + sliceIntrinsic = jsSliceArrayLikeFromIndex + } + if (numberOfTrailingParameters > 0) { + sliceIntrinsicArgs.add(irGet(firstTrailingParameterIndexVar.value)) + sliceIntrinsic = jsSliceArrayLikeFromIndexToIndex + } + + val varargCopiedAsArray = irCall(sliceIntrinsic).apply { + putTypeArgument(0, varargElement.varargElementType!!) + sliceIntrinsicArgs.forEachIndexed(this::putValueArgument) + }.let { arrayExpr -> + val arrayInfo = + InlineClassArrayInfo(this@JsBridgesConstruction.context, varargElement.varargElementType!!, varargElement.type) + val primitiveType = primitiveArrays[arrayInfo.primitiveArrayType.classifierOrNull] + if (primitiveType != null) { + arrayInfo.boxArrayIfNeeded( + irCall(primitiveToLiteralConstructor.getValue(primitiveType)).apply { + putValueArgument(0, arrayExpr) + type = varargElement.type + } + ) + } else { + arrayExpr + } + } + + return createTmpVariable(varargCopiedAsArray, nameHint = varargElement.name.asString()) + } } interface JsSignature { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/VarargLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/VarargLowering.kt index 6444e7357e9..344e516355d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/VarargLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/VarargLowering.kt @@ -6,8 +6,10 @@ package org.jetbrains.kotlin.ir.backend.js.lower import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.compilationException import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl @@ -19,7 +21,6 @@ import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.util.constructors import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.getInlineClassBackingField -import org.jetbrains.kotlin.ir.util.getInlineClassUnderlyingType import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -34,92 +35,15 @@ private class VarargTransformer( val context: JsIrBackendContext ) : IrElementTransformerVoid() { - fun IrType.getInlinedClass() = context.inlineClassesUtils.getInlinedClass(this) - var externalVarargs = mutableSetOf() - private fun List.toArrayLiteral(type: IrType, varargElementType: IrType): IrExpression { - - // TODO: Use symbols when builtins symbol table is fixes - val primitiveType = context.intrinsics.primitiveArrays.mapKeys { it.key }[type.classifierOrNull] - - val intrinsic = - if (primitiveType != null) - context.intrinsics.primitiveToLiteralConstructor.getValue(primitiveType) - else - context.intrinsics.arrayLiteral - - val startOffset = firstOrNull()?.startOffset ?: UNDEFINED_OFFSET - val endOffset = lastOrNull()?.endOffset ?: UNDEFINED_OFFSET - - val irVararg = IrVarargImpl(startOffset, endOffset, type, varargElementType, this) - - return IrCallImpl( - startOffset, endOffset, - type, intrinsic, - typeArgumentsCount = if (intrinsic.owner.typeParameters.isNotEmpty()) 1 else 0, - valueArgumentsCount = 1 - ).apply { - if (typeArgumentsCount == 1) { - putTypeArgument(0, varargElementType) - } - putValueArgument(0, irVararg) - } - } - - inner class InlineClassArrayInfo( - val elementType: IrType, - val arrayType: IrType - ) { - val arrayInlineClass = arrayType.getInlinedClass() - val inlined = arrayInlineClass != null - - val primitiveElementType = when { - inlined -> getInlineClassUnderlyingType(elementType.getInlinedClass()!!) - else -> elementType - } - - val primitiveArrayType = when { - inlined -> getInlineClassUnderlyingType(arrayInlineClass!!) - else -> arrayType - } - - fun boxArrayIfNeeded(array: IrExpression) = - if (arrayInlineClass == null) - array - else with(array) { - IrConstructorCallImpl.fromSymbolOwner( - startOffset, - endOffset, - arrayInlineClass.defaultType, - arrayInlineClass.constructors.single { it.isPrimary }.symbol, - arrayInlineClass.typeParameters.size - ).also { - it.putValueArgument(0, array) - } - } - - fun unboxElementIfNeeded(element: IrExpression): IrExpression { - if (arrayInlineClass == null) - return element - else with(element) { - val inlinedClass = type.getInlinedClass() ?: return element - val field = getInlineClassBackingField(inlinedClass) - return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this) - } - } - - fun toPrimitiveArrayLiteral(elements: List) = - elements.toArrayLiteral(primitiveArrayType, primitiveElementType) - } - override fun visitVararg(expression: IrVararg): IrExpression { expression.transformChildrenVoid(this) val currentList = mutableListOf() val segments = mutableListOf() - val arrayInfo = InlineClassArrayInfo(expression.varargElementType, expression.type) + val arrayInfo = InlineClassArrayInfo(context, expression.varargElementType, expression.type) for (e in expression.elements) { when (e) { @@ -162,6 +86,7 @@ private class VarargTransformer( val arrayLiteral = segments.toArrayLiteral( + context, IrSimpleTypeImpl(context.intrinsics.array, false, emptyList(), emptyList()), // TODO: Substitution context.irBuiltIns.anyType ) @@ -218,7 +143,7 @@ private class VarargTransformer( } else segment } - private fun transformFunctionAccessExpression(expression: IrFunctionAccessExpression): IrExpression { + override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { expression.transformChildrenVoid() if (expression.symbol.owner.isExternal) { @@ -240,7 +165,7 @@ private class VarargTransformer( val parameter = expression.symbol.owner.valueParameters[i] val varargElementType = parameter.varargElementType if (argument == null && varargElementType != null) { - val arrayInfo = InlineClassArrayInfo(varargElementType, parameter.type) + val arrayInfo = InlineClassArrayInfo(context, varargElementType, parameter.type) val emptyArray = with(arrayInfo) { boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList())) } @@ -251,7 +176,85 @@ private class VarargTransformer( return expression } - - override fun visitFunctionAccess(expression: IrFunctionAccessExpression) = - transformFunctionAccessExpression(expression) +} + +private fun List.toArrayLiteral(context: JsIrBackendContext, type: IrType, varargElementType: IrType): IrExpression { + + // TODO: Use symbols when builtins symbol table is fixes + val primitiveType = context.intrinsics.primitiveArrays.mapKeys { it.key }[type.classifierOrNull] + + val intrinsic = + if (primitiveType != null) + context.intrinsics.primitiveToLiteralConstructor.getValue(primitiveType) + else + context.intrinsics.arrayLiteral + + val startOffset = firstOrNull()?.startOffset ?: UNDEFINED_OFFSET + val endOffset = lastOrNull()?.endOffset ?: UNDEFINED_OFFSET + + val irVararg = IrVarargImpl(startOffset, endOffset, type, varargElementType, this) + + return IrCallImpl( + startOffset, endOffset, + type, intrinsic, + typeArgumentsCount = if (intrinsic.owner.typeParameters.isNotEmpty()) 1 else 0, + valueArgumentsCount = 1 + ).apply { + if (typeArgumentsCount == 1) { + putTypeArgument(0, varargElementType) + } + putValueArgument(0, irVararg) + } +} + +internal class InlineClassArrayInfo(val context: JsIrBackendContext, val elementType: IrType, val arrayType: IrType) { + + private fun IrType.getInlinedClass() = context.inlineClassesUtils.getInlinedClass(this) + private fun getInlineClassUnderlyingType(irClass: IrClass) = context.inlineClassesUtils.getInlineClassUnderlyingType(irClass) + + val arrayInlineClass = arrayType.getInlinedClass() + val inlined = arrayInlineClass != null + + val primitiveElementType = when { + inlined -> getInlineClassUnderlyingType( + elementType.getInlinedClass() ?: compilationException( + "Could not get inlined class", + elementType + ) + ) + else -> elementType + } + + val primitiveArrayType = when { + inlined -> getInlineClassUnderlyingType(arrayInlineClass!!) + else -> arrayType + } + + fun boxArrayIfNeeded(array: IrExpression) = + if (arrayInlineClass == null) + array + else with(array) { + IrConstructorCallImpl.fromSymbolOwner( + startOffset, + endOffset, + arrayInlineClass.defaultType, + arrayInlineClass.constructors.single { it.isPrimary }.symbol, + arrayInlineClass.typeParameters.size + ).also { + it.putValueArgument(0, array) + } + } + + fun unboxElementIfNeeded(element: IrExpression): IrExpression { + if (arrayInlineClass == null) + return element + else with(element) { + val inlinedClass = type.getInlinedClass() ?: return element + val field = getInlineClassBackingField(inlinedClass) + return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this) + } + } + + fun toPrimitiveArrayLiteral(elements: List) = + elements.toArrayLiteral(context, primitiveArrayType, primitiveElementType) } 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 464b39cd4a2..9b9999f7b8a 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 @@ -319,6 +319,9 @@ fun argumentsWithVarargAsSingleArray( } } +/** + * Returns the index of the vararg parameter of the function if there is one, otherwise returns -1. + */ fun IrFunction.varargParameterIndex() = valueParameters.indexOfFirst { it.varargElementType != null } fun translateCallArguments( diff --git a/compiler/testData/codegen/box/bridges/substitutionInSuperClass/simple.kt b/compiler/testData/codegen/box/bridges/substitutionInSuperClass/simple.kt index c6c2637ef22..297552fcd29 100644 --- a/compiler/testData/codegen/box/bridges/substitutionInSuperClass/simple.kt +++ b/compiler/testData/codegen/box/bridges/substitutionInSuperClass/simple.kt @@ -1,11 +1,11 @@ open class A { - open fun foo(t: T) = "A" + open fun foo(t: T, vararg xs: Int) = "A" } open class B : A() class Z : B() { - override fun foo(t: String) = "Z" + override fun foo(t: String, vararg xs: Int) = "Z" } diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 031fff1d748..286d6a5dab9 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -9822,6 +9822,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/vararg"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @Test + @TestMetadata("jsExternalInterfaceVararg.kt") + public void testJsExternalInterfaceVararg() throws Exception { + runTest("js/js.translator/testData/box/vararg/jsExternalInterfaceVararg.kt"); + } + @Test @TestMetadata("jsExternalVarargCtor.kt") public void testJsExternalVarargCtor() throws Exception { diff --git a/js/js.translator/testData/box/vararg/jsExternalInterfaceVararg.kt b/js/js.translator/testData/box/vararg/jsExternalInterfaceVararg.kt new file mode 100644 index 00000000000..8ea1e699d43 --- /dev/null +++ b/js/js.translator/testData/box/vararg/jsExternalInterfaceVararg.kt @@ -0,0 +1,159 @@ +// DONT_TARGET_EXACT_BACKEND: JS +// KJS_WITH_FULL_RUNTIME + +// Generated briges must not contain any casts! +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=collect1 +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=collect2 +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=collect3 +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=collect4 +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=collect4 +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumBools +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumBytes +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumShorts +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumChars +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumInts +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumFloats +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumDoubles +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumLongs +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumUByte +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumUShort +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumUInt +// CHECK_NOT_CALLED_IN_SCOPE: function=THROW_CCE scope=sumULong + +external interface Collector { + + // Both leading and trailing parameters are present + fun collect1(leading1: String, leading2: String, vararg xs: String, trailing1: String, trailing2: String, trailing3: String): String + + // No trailing parameters + fun collect2(leading: String, vararg xs: String): String + + // No leading parameters + fun collect3(vararg xs: String, trailing: String): String + + // Vararg only + fun collect4(vararg xs: String): String +} + +class CollectorImpl : Collector { + override fun collect1( + leading1: String, + leading2: String, + vararg xs: String, + trailing1: String, + trailing2: String, + trailing3: String + ): String { + var result = "[$leading1, $leading2, [" + for (i in 0 until xs.size) { + if (i > 0) result += ", " + result += xs[i] + } + result += "], $trailing1, $trailing2, $trailing3]" + return result + } + + override fun collect2(leading: String, vararg xs: String) = + collect1("∅", leading, *xs, trailing1 = "∅", trailing2 = "∅", trailing3 = "∅") + + override fun collect3(vararg xs: String, trailing: String) = + collect1("∅", "∅", *xs, trailing1 = trailing, trailing2 = "∅", trailing3 = "∅") + + override fun collect4(vararg xs: String) = collect1("∅", "∅", *xs, trailing1 = "∅", trailing2 = "∅", trailing3 = "∅") +} + +fun collectNames1(collector: Collector, names: Array) = + collector.collect1("Jack", "Kate", *names, trailing2 = "Winsent", trailing1 = "Claire", trailing3 = "Sawyer") + +fun collectNames2(collector: Collector, names: Array) = + collector.collect2("Jack", *names) + +fun collectNames3(collector: Collector, names: Array) = + collector.collect3(*names, trailing = "Winsent") + +fun collectNames4(collector: Collector, names: Array) = + collector.collect4(*names) + +external interface Folder { + fun sumBools(vararg xs: Boolean): Boolean + fun sumBytes(vararg xs: Byte): Int + fun sumShorts(vararg xs: Short): Int + fun sumChars(vararg xs: Char): String + fun sumInts(vararg xs: Int): Int + fun sumFloats(vararg xs: Float): Float + fun sumDoubles(vararg xs: Double): Double + fun sumLongs(vararg xs: Long): Long + + fun sumUByte(vararg xs: UByte): UInt + fun sumUShort(vararg xs: UShort): UInt + fun sumUInt(vararg xs: UInt): UInt + fun sumULong(vararg xs: ULong): ULong +} + +class FolderImpl: Folder { + + // Test with primitive types. Varargs of primitive types expect primitive arrays. + override fun sumBools(vararg xs: Boolean) = xs.fold(true, Boolean::or) + override fun sumBytes(vararg xs: Byte) = xs.fold(0, Int::plus) + override fun sumShorts(vararg xs: Short) = xs.fold(0, Int::plus) + override fun sumChars(vararg xs: Char) = xs.fold("", String::plus) + override fun sumInts(vararg xs: Int) = xs.fold(0, Int::plus) + override fun sumFloats(vararg xs: Float) = xs.fold(0.0f, Float::plus) + override fun sumDoubles(vararg xs: Double) = xs.fold(0.0, Double::plus) + override fun sumLongs(vararg xs: Long) = xs.fold(0L, Long::plus) + + // Test with inline classes + override fun sumUByte(vararg xs: UByte) = xs.fold(0U, UInt::plus) + override fun sumUShort(vararg xs: UShort) = xs.fold(0U, UInt::plus) + override fun sumUInt(vararg xs: UInt) = xs.fold(0U, UInt::plus) + override fun sumULong(vararg xs: ULong) = xs.fold(0UL, ULong::plus) +} + +fun box(): String { + val collector: Collector = CollectorImpl() + + assertEquals("[1, 2, [3, 4], 5, 6, 7]", collector.collect1("1", "2", "3", "4", trailing2 = "6", trailing1 = "5", trailing3 = "7")) + assertEquals("[∅, 1, [2, 3, 4, 5, 6, 7], ∅, ∅, ∅]", collector.collect2("1", "2", "3", "4", "5", "6", "7")) + assertEquals("[∅, ∅, [1, 2, 3], 4, ∅, ∅]", collector.collect3("1", "2", "3", trailing = "4")) + assertEquals("[∅, ∅, [], ∅, ∅, ∅]", collector.collect4()) + + assertEquals("[Jack, Kate, [Hugo, Jin], Claire, Winsent, Sawyer]", collectNames1(collector, arrayOf("Hugo", "Jin"))) + assertEquals("[∅, Jack, [Kate, Hugo, Jin], ∅, ∅, ∅]", collectNames2(collector, arrayOf("Kate", "Hugo", "Jin"))) + assertEquals("[∅, ∅, [Claire], Winsent, ∅, ∅]", collectNames3(collector, arrayOf("Claire"))) + assertEquals("[∅, ∅, [], ∅, ∅, ∅]", collectNames4(collector, arrayOf())) + + assertEquals( + "[4, 8, [15], 16, 23, 42]", + js("collector.collect1('4', '8', '15', '16', '23', '42')").unsafeCast() + ) + assertEquals( + "[∅, 4, [8, 15, 16, 23, 42], ∅, ∅, ∅]", + js("collector.collect2('4', '8', '15', '16', '23', '42')").unsafeCast() + ) + assertEquals( + "[∅, ∅, [4, 8, 15, 16, 23], 42, ∅, ∅]", + js("collector.collect3('4', '8', '15', '16', '23', '42')").unsafeCast() + ) + assertEquals( + "[∅, ∅, [], ∅, ∅, ∅]", + js("collector.collect4()").unsafeCast() + ) + + val folder: Folder = FolderImpl() + + assertEquals(true, folder.sumBools(false, true, false, false)) + assertEquals(10, folder.sumBytes(1, 2, 3, 4)) + assertEquals(10, folder.sumShorts(1, 2, 3, 4)) + assertEquals("Hello", folder.sumChars('H', 'e', 'l', 'l', 'o')) + assertEquals(10, folder.sumInts(1, 2, 3, 4)) + assertEquals(10.0f, folder.sumFloats(1.0f, 2.0f, 3.0f, 4.0f)) + assertEquals(10.0, folder.sumDoubles(1.0, 2.0, 3.0, 4.0)) + assertEquals(10L, folder.sumLongs(1L, 2L, 3L, 4L)) + + assertEquals(10U, folder.sumUByte(1U, 2U, 3U, 4U)) + assertEquals(10U, folder.sumUShort(1U, 2U, 3U, 4U)) + assertEquals(10U, folder.sumUInt(1U, 2U, 3U, 4U)) + assertEquals(10U, folder.sumULong(1U, 2U, 3U, 4U)) + + return "OK" +}