[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
This commit is contained in:
+55
-28
@@ -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<T> {
|
||||
// fun foo(t: T) = ...
|
||||
// }
|
||||
//
|
||||
// class D : C<Int> {
|
||||
// override fun foo(t: Int) = impl
|
||||
// }
|
||||
//
|
||||
// it adds method D that delegates generic calls to implementation:
|
||||
//
|
||||
// class D : C<Int> {
|
||||
// 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<T> {
|
||||
* fun foo(t: T) = ...
|
||||
* }
|
||||
*
|
||||
* class D : C<Int> {
|
||||
* override fun foo(t: Int) = impl
|
||||
* }
|
||||
*
|
||||
* it adds method D that delegates generic calls to implementation:
|
||||
*
|
||||
* class D : C<Int> {
|
||||
* override fun foo(t: Int) = impl
|
||||
* fun foo(t: Any?) = foo(t as Int) // Constructed bridge
|
||||
* }
|
||||
*/
|
||||
abstract class BridgesConstruction<T : JsCommonBackendContext>(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<IrValueDeclaration> = irFunction.valueParameters
|
||||
|
||||
// Should dispatch receiver type be casted inside a bridge.
|
||||
open val shouldCastDispatchReceiver: Boolean = false
|
||||
|
||||
@@ -131,7 +144,7 @@ abstract class BridgesConstruction<T : JsCommonBackendContext>(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<T : JsCommonBackendContext>(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<T : JsCommonBackendContext>(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<T : JsCommonBackendContext>(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<IrTypeParameterSymbol, IrType>) {
|
||||
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) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+127
-2
@@ -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<JsIrBackendContext>(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<J
|
||||
JsLoweredDeclarationOrigin.BRIDGE_WITH_STABLE_NAME
|
||||
else
|
||||
JsLoweredDeclarationOrigin.BRIDGE_WITHOUT_STABLE_NAME
|
||||
|
||||
override fun extractValueParameters(
|
||||
blockBodyBuilder: IrBlockBodyBuilder,
|
||||
irFunction: IrSimpleFunction,
|
||||
bridge: IrSimpleFunction
|
||||
): List<IrValueDeclaration> {
|
||||
|
||||
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<IrVariable>
|
||||
) = 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>
|
||||
): IrVariable {
|
||||
val varargElement = bridge.valueParameters[varargIndex]
|
||||
val sliceIntrinsicArgs = mutableListOf<IrExpression>(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 {
|
||||
|
||||
+87
-84
@@ -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<IrVararg>()
|
||||
|
||||
private fun List<IrExpression>.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<IrExpression>) =
|
||||
elements.toArrayLiteral(primitiveArrayType, primitiveElementType)
|
||||
}
|
||||
|
||||
override fun visitVararg(expression: IrVararg): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
|
||||
val currentList = mutableListOf<IrExpression>()
|
||||
val segments = mutableListOf<IrExpression>()
|
||||
|
||||
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<IrExpression>.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<IrExpression>) =
|
||||
elements.toArrayLiteral(context, primitiveArrayType, primitiveElementType)
|
||||
}
|
||||
|
||||
+3
@@ -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(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
open class A<T> {
|
||||
open fun foo(t: T) = "A"
|
||||
open fun foo(t: T, vararg xs: Int) = "A"
|
||||
}
|
||||
|
||||
open class B : A<String>()
|
||||
|
||||
class Z : B() {
|
||||
override fun foo(t: String) = "Z"
|
||||
override fun foo(t: String, vararg xs: Int) = "Z"
|
||||
}
|
||||
|
||||
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
@@ -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<String>) =
|
||||
collector.collect1("Jack", "Kate", *names, trailing2 = "Winsent", trailing1 = "Claire", trailing3 = "Sawyer")
|
||||
|
||||
fun collectNames2(collector: Collector, names: Array<String>) =
|
||||
collector.collect2("Jack", *names)
|
||||
|
||||
fun collectNames3(collector: Collector, names: Array<String>) =
|
||||
collector.collect3(*names, trailing = "Winsent")
|
||||
|
||||
fun collectNames4(collector: Collector, names: Array<String>) =
|
||||
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<String>()
|
||||
)
|
||||
assertEquals(
|
||||
"[∅, 4, [8, 15, 16, 23, 42], ∅, ∅, ∅]",
|
||||
js("collector.collect2('4', '8', '15', '16', '23', '42')").unsafeCast<String>()
|
||||
)
|
||||
assertEquals(
|
||||
"[∅, ∅, [4, 8, 15, 16, 23], 42, ∅, ∅]",
|
||||
js("collector.collect3('4', '8', '15', '16', '23', '42')").unsafeCast<String>()
|
||||
)
|
||||
assertEquals(
|
||||
"[∅, ∅, [], ∅, ∅, ∅]",
|
||||
js("collector.collect4()").unsafeCast<String>()
|
||||
)
|
||||
|
||||
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"
|
||||
}
|
||||
Reference in New Issue
Block a user