From 59617c97b4d509f0a80dedf76142e8ab2576827c Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Sun, 19 May 2019 01:27:34 +0300 Subject: [PATCH] [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 --- .../ir/backend/js/lower/VarargLowering.kt | 115 ++++++++++------- .../entryClassConstructorWithVarargs.kt | 1 - .../ir/semantics/IrBoxJsTestGenerated.java | 18 +++ .../js/test/semantics/BoxJsTestGenerated.java | 18 +++ .../emptyVarargInConstructorCall.kt | 117 ++++++++++++++++++ 5 files changed, 220 insertions(+), 49 deletions(-) create mode 100644 js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt 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 b61523887a9..856c7d34843 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 @@ -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) = + elements.toArrayLiteral(primitiveArrayType, primitiveElementType) + } + override fun visitVararg(expression: IrVararg): IrExpression { expression.transformChildrenVoid(this) val currentList = mutableListOf() val segments = mutableListOf() - 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().toArrayLiteral(primitiveExpressionType, primitiveElementType) - return if (needUnboxing) - res.boxInlineClassIfNeeded(arrayInlineClass!!) - else - res + with (arrayInfo) { + return boxArrayIfNeeded(toPrimitiveArrayLiteral(emptyList())) + } } // 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().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())) + } + + 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) } diff --git a/compiler/testData/codegen/box/enum/defaultCtor/entryClassConstructorWithVarargs.kt b/compiler/testData/codegen/box/enum/defaultCtor/entryClassConstructorWithVarargs.kt index 9ca5458b989..b5d7511a0b0 100644 --- a/compiler/testData/codegen/box/enum/defaultCtor/entryClassConstructorWithVarargs.kt +++ b/compiler/testData/codegen/box/enum/defaultCtor/entryClassConstructorWithVarargs.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index ca11b803942..b5bc6ab2627 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -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") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 64edc48d1b1..e787fbbdeb5 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -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") diff --git a/js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt b/js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt new file mode 100644 index 00000000000..b92754650ad --- /dev/null +++ b/js/js.translator/testData/box/regression/typeChecks/emptyVarargInConstructorCall.kt @@ -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() +} \ No newline at end of file