[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:
Sergej Jaskiewicz
2021-12-22 20:56:19 +03:00
committed by Space
parent d5e46e0607
commit 4a724611d8
7 changed files with 439 additions and 116 deletions
@@ -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) }
}
@@ -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 {
@@ -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)
}
@@ -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"
}
@@ -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"
}