[JS IR BE] Fix VarargLowering
- Enable empty vararg transformation for all IrFunctionAccessExpression - Fix empty vararg boxing - Refactor inline class boxing logic - Fix types of generated expressions
This commit is contained in:
+67
-48
@@ -8,8 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
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.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.name
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||
@@ -55,68 +55,84 @@ private class VarargTransformer(
|
||||
}
|
||||
}
|
||||
|
||||
fun IrExpression.unboxInlineClassIfNeeded(): IrExpression {
|
||||
val inlinedClass = type.getInlinedClass() ?: return this
|
||||
val field = getInlineClassBackingField(inlinedClass)
|
||||
return IrGetFieldImpl(startOffset, endOffset, field.symbol, field.type, this)
|
||||
}
|
||||
inner class InlineClassArrayInfo(
|
||||
val elementType: IrType,
|
||||
val arrayType: IrType
|
||||
) {
|
||||
val arrayInlineClass = arrayType.getInlinedClass()
|
||||
val inlined = arrayInlineClass != null
|
||||
|
||||
fun IrExpression.boxInlineClassIfNeeded(inlineClass: IrClass?) =
|
||||
if (inlineClass == null)
|
||||
this
|
||||
else
|
||||
IrConstructorCallImpl.fromSymbolOwner(startOffset, endOffset, inlineClass.defaultType, inlineClass.constructors.single { it.isPrimary }.symbol).also {
|
||||
it.putValueArgument(0, this)
|
||||
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
|
||||
).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 elementType = expression.varargElementType
|
||||
val primitiveElementType: IrType
|
||||
val primitiveExpressionType: IrType
|
||||
val needUnboxing: Boolean
|
||||
val arrayInlineClass = expression.type.getInlinedClass()
|
||||
if (arrayInlineClass != null) {
|
||||
primitiveElementType = getInlineClassUnderlyingType(elementType.getInlinedClass()!!)
|
||||
primitiveExpressionType = getInlineClassUnderlyingType(arrayInlineClass)
|
||||
needUnboxing = true
|
||||
} else {
|
||||
primitiveElementType = elementType
|
||||
primitiveExpressionType = expression.type
|
||||
needUnboxing = false
|
||||
}
|
||||
val arrayInfo = InlineClassArrayInfo(expression.varargElementType, expression.type)
|
||||
|
||||
for (e in expression.elements) {
|
||||
when (e) {
|
||||
is IrSpreadElement -> {
|
||||
if (!currentList.isEmpty()) {
|
||||
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
|
||||
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
|
||||
currentList.clear()
|
||||
}
|
||||
segments.add(if (needUnboxing) e.expression.unboxInlineClassIfNeeded() else e.expression)
|
||||
segments.add(arrayInfo.unboxElementIfNeeded(e.expression))
|
||||
}
|
||||
|
||||
is IrExpression -> {
|
||||
currentList.add(if (needUnboxing) e.unboxInlineClassIfNeeded() else e)
|
||||
currentList.add(arrayInfo.unboxElementIfNeeded(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!currentList.isEmpty()) {
|
||||
segments.add(currentList.toArrayLiteral(primitiveExpressionType, primitiveElementType))
|
||||
segments.add(arrayInfo.toPrimitiveArrayLiteral(currentList))
|
||||
currentList.clear()
|
||||
}
|
||||
|
||||
// empty vararg => empty array literal
|
||||
if (segments.isEmpty()) {
|
||||
val res = emptyList<IrExpression>().toArrayLiteral(primitiveExpressionType, primitiveElementType)
|
||||
return if (needUnboxing)
|
||||
res.boxInlineClassIfNeeded(arrayInlineClass!!)
|
||||
else
|
||||
res
|
||||
with (arrayInfo) {
|
||||
return boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList<IrExpression>()))
|
||||
}
|
||||
}
|
||||
|
||||
// vararg with a single segment => no need to concatenate
|
||||
@@ -126,15 +142,15 @@ private class VarargTransformer(
|
||||
IrCallImpl(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
expression.type,
|
||||
arrayInfo.primitiveArrayType,
|
||||
context.intrinsics.jsArraySlice
|
||||
).apply {
|
||||
putTypeArgument(0, expression.type)
|
||||
putTypeArgument(0, arrayInfo.primitiveArrayType)
|
||||
putValueArgument(0, segment)
|
||||
}
|
||||
} else segment
|
||||
|
||||
return if (needUnboxing) argument.boxInlineClassIfNeeded(arrayInlineClass!!) else argument
|
||||
return arrayInfo.boxArrayIfNeeded(argument)
|
||||
}
|
||||
|
||||
val arrayLiteral =
|
||||
@@ -143,7 +159,7 @@ private class VarargTransformer(
|
||||
context.irBuiltIns.anyType
|
||||
)
|
||||
|
||||
val concatFun = if (expression.type.classifierOrNull in context.intrinsics.primitiveArrays.keys) {
|
||||
val concatFun = if (arrayInfo.primitiveArrayType.classifierOrNull in context.intrinsics.primitiveArrays.keys) {
|
||||
context.intrinsics.primitiveArrayConcat
|
||||
} else {
|
||||
context.intrinsics.arrayConcat
|
||||
@@ -152,16 +168,13 @@ private class VarargTransformer(
|
||||
val res = IrCallImpl(
|
||||
expression.startOffset,
|
||||
expression.endOffset,
|
||||
expression.type,
|
||||
arrayInfo.primitiveArrayType,
|
||||
concatFun
|
||||
).apply {
|
||||
putValueArgument(0, arrayLiteral)
|
||||
}
|
||||
|
||||
return if (needUnboxing)
|
||||
res.boxInlineClassIfNeeded(arrayInlineClass!!)
|
||||
else
|
||||
res
|
||||
return arrayInfo.boxArrayIfNeeded(res)
|
||||
}
|
||||
|
||||
private fun transformFunctionAccessExpression(expression: IrFunctionAccessExpression): IrExpression {
|
||||
@@ -171,14 +184,20 @@ private class VarargTransformer(
|
||||
for (i in 0 until size) {
|
||||
val argument = expression.getValueArgument(i)
|
||||
val parameter = expression.symbol.owner.valueParameters[i]
|
||||
if (argument == null && parameter.varargElementType != null) {
|
||||
expression.putValueArgument(i, emptyList<IrExpression>().toArrayLiteral(parameter.type, parameter.varargElementType!!))
|
||||
val varargElementType = parameter.varargElementType
|
||||
if (argument == null && varargElementType != null) {
|
||||
val arrayInfo = InlineClassArrayInfo(varargElementType, parameter.type)
|
||||
val emptyArray = with (arrayInfo) {
|
||||
boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList<IrExpression>()))
|
||||
}
|
||||
|
||||
expression.putValueArgument(i, emptyArray)
|
||||
}
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall) = transformFunctionAccessExpression(expression)
|
||||
override fun visitConstructorCall(expression: IrConstructorCall) = transformFunctionAccessExpression(expression)
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) =
|
||||
transformFunctionAccessExpression(expression)
|
||||
}
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
|
||||
|
||||
+18
@@ -6778,6 +6778,24 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/regression/typeChecks")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TypeChecks extends AbstractIrBoxJsTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInTypeChecks() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/typeChecks"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("emptyVarargInConstructorCall.kt")
|
||||
public void testEmptyVarargInConstructorCall() throws Exception {
|
||||
runTest("js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/reified")
|
||||
|
||||
+18
@@ -6813,6 +6813,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/regression/stdlibTestSnippets/throwable.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/regression/typeChecks")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TypeChecks extends AbstractBoxJsTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInTypeChecks() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/regression/typeChecks"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS, true);
|
||||
}
|
||||
|
||||
@TestMetadata("emptyVarargInConstructorCall.kt")
|
||||
public void testEmptyVarargInConstructorCall() throws Exception {
|
||||
runTest("js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/reified")
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
// KJS_WITH_FULL_RUNTIME
|
||||
// EXPECTED_REACHABLE_NODES: 1857
|
||||
|
||||
package foo
|
||||
|
||||
open class AByte(vararg val x: Byte)
|
||||
class BByte : AByte()
|
||||
|
||||
open class AShort(vararg val x: Short)
|
||||
class BShort : AShort()
|
||||
|
||||
open class AInt(vararg val x: Int)
|
||||
class BInt : AInt()
|
||||
|
||||
open class AChar(vararg val x: Char)
|
||||
class BChar : AChar()
|
||||
|
||||
open class ALong(vararg val x: Long)
|
||||
class BLong : ALong()
|
||||
|
||||
open class AFloat(vararg val x: Float)
|
||||
class BFloat : AFloat()
|
||||
|
||||
open class ADouble(vararg val x: Double)
|
||||
class BDouble : ADouble()
|
||||
|
||||
open class AUByte(vararg val x: UByte)
|
||||
class BUByte : AUByte()
|
||||
|
||||
open class AUShort(vararg val x: UShort)
|
||||
class BUShort : AUShort()
|
||||
|
||||
open class AUInt(vararg val x: UInt)
|
||||
class BUInt : AUInt()
|
||||
|
||||
open class AULong(vararg val x: ULong)
|
||||
class BULong : AULong()
|
||||
|
||||
open class ABoolean(vararg val x: Boolean)
|
||||
class BBoolean : ABoolean()
|
||||
|
||||
fun box(): String {
|
||||
val a_x_Byte = AByte().x as Any as ByteArray
|
||||
if (a_x_Byte.size != 0) return "Fail a ByteArray"
|
||||
|
||||
val b_x_Byte = BByte().x as Any as ByteArray
|
||||
if (b_x_Byte.size != 0) return "Fail b ByteArray"
|
||||
|
||||
val a_x_Short = AShort().x as Any as ShortArray
|
||||
if (a_x_Short.size != 0) return "Fail a ShortArray"
|
||||
|
||||
val b_x_Short = BShort().x as Any as ShortArray
|
||||
if (b_x_Short.size != 0) return "Fail b ShortArray"
|
||||
|
||||
val a_x_Int = AInt().x as Any as IntArray
|
||||
if (a_x_Int.size != 0) return "Fail a IntArray"
|
||||
|
||||
val b_x_Int = BInt().x as Any as IntArray
|
||||
if (b_x_Int.size != 0) return "Fail b IntArray"
|
||||
|
||||
val a_x_Char = AChar().x as Any as CharArray
|
||||
if (a_x_Char.size != 0) return "Fail a CharArray"
|
||||
|
||||
val b_x_Char = BChar().x as Any as CharArray
|
||||
if (b_x_Char.size != 0) return "Fail b CharArray"
|
||||
|
||||
val a_x_Long = ALong().x as Any as LongArray
|
||||
if (a_x_Long.size != 0) return "Fail a LongArray"
|
||||
|
||||
val b_x_Long = BLong().x as Any as LongArray
|
||||
if (b_x_Long.size != 0) return "Fail b LongArray"
|
||||
|
||||
val a_x_Float = AFloat().x as Any as FloatArray
|
||||
if (a_x_Float.size != 0) return "Fail a FloatArray"
|
||||
|
||||
val b_x_Float = BFloat().x as Any as FloatArray
|
||||
if (b_x_Float.size != 0) return "Fail b FloatArray"
|
||||
|
||||
val a_x_Double = ADouble().x as Any as DoubleArray
|
||||
if (a_x_Double.size != 0) return "Fail a DoubleArray"
|
||||
|
||||
val b_x_Double = BDouble().x as Any as DoubleArray
|
||||
if (b_x_Double.size != 0) return "Fail b DoubleArray"
|
||||
|
||||
val a_x_UByte = AUByte().x as Any as UByteArray
|
||||
if (a_x_UByte.size != 0) return "Fail a UByteArray"
|
||||
|
||||
val b_x_UByte = BUByte().x as Any as UByteArray
|
||||
if (b_x_UByte.size != 0) return "Fail b UByteArray"
|
||||
|
||||
val a_x_UShort = AUShort().x as Any as UShortArray
|
||||
if (a_x_UShort.size != 0) return "Fail a UShortArray"
|
||||
|
||||
val b_x_UShort = BUShort().x as Any as UShortArray
|
||||
if (b_x_UShort.size != 0) return "Fail b UShortArray"
|
||||
|
||||
val a_x_UInt = AUInt().x as Any as UIntArray
|
||||
if (a_x_UInt.size != 0) return "Fail a UIntArray"
|
||||
|
||||
val b_x_UInt = BUInt().x as Any as UIntArray
|
||||
if (b_x_UInt.size != 0) return "Fail b UIntArray"
|
||||
|
||||
val a_x_ULong = AULong().x as Any as ULongArray
|
||||
if (a_x_ULong.size != 0) return "Fail a ULongArray"
|
||||
|
||||
val b_x_ULong = BULong().x as Any as ULongArray
|
||||
if (b_x_ULong.size != 0) return "Fail b ULongArray"
|
||||
|
||||
val a_x_Boolean = ABoolean().x as Any as BooleanArray
|
||||
if (a_x_Boolean.size != 0) return "Fail a BooleanArray"
|
||||
|
||||
val b_x_Boolean = BBoolean().x as Any as BooleanArray
|
||||
if (b_x_Boolean.size != 0) return "Fail b BooleanArray"
|
||||
|
||||
return "OK"
|
||||
// return Local().obj.result()
|
||||
}
|
||||
Reference in New Issue
Block a user