From 83588e9f221d6dcca95ba297d446acd9c5f758ee Mon Sep 17 00:00:00 2001 From: Mads Ager Date: Mon, 30 Nov 2020 16:41:07 +0100 Subject: [PATCH] [JVM_IR] Add tests of Compose-like default argument handling. --- .../testData/codegen/composeLike/default.kt | 4 + .../codegen/composeLike/defaultInline.kt | 3 + .../codegen/composeLike/defaultLocal.kt | 4 + ...bstractComposeLikeIrBlackBoxCodegenTest.kt | 336 ++++++++++++++++++ ...oseLikeIrBlackBoxCodegenTestGenerated.java | 46 +++ .../generators/tests/GenerateCompilerTests.kt | 6 +- 6 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/codegen/composeLike/default.kt create mode 100644 compiler/testData/codegen/composeLike/defaultInline.kt create mode 100644 compiler/testData/codegen/composeLike/defaultLocal.kt create mode 100644 compiler/tests-common/tests/org/jetbrains/kotlin/codegen/ir/AbstractComposeLikeIrBlackBoxCodegenTest.kt create mode 100644 compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/ComposeLikeIrBlackBoxCodegenTestGenerated.java diff --git a/compiler/testData/codegen/composeLike/default.kt b/compiler/testData/codegen/composeLike/default.kt new file mode 100644 index 00000000000..6f2dbd0bdea --- /dev/null +++ b/compiler/testData/codegen/composeLike/default.kt @@ -0,0 +1,4 @@ +fun foo(s: String = "O") = s + +fun box() = foo() + foo("K") + diff --git a/compiler/testData/codegen/composeLike/defaultInline.kt b/compiler/testData/codegen/composeLike/defaultInline.kt new file mode 100644 index 00000000000..48384ba3361 --- /dev/null +++ b/compiler/testData/codegen/composeLike/defaultInline.kt @@ -0,0 +1,3 @@ +inline fun foo(s: String = "O") = s + +fun box() = foo() + foo("K") diff --git a/compiler/testData/codegen/composeLike/defaultLocal.kt b/compiler/testData/codegen/composeLike/defaultLocal.kt new file mode 100644 index 00000000000..d3b1c75bc67 --- /dev/null +++ b/compiler/testData/codegen/composeLike/defaultLocal.kt @@ -0,0 +1,4 @@ +fun box(): String { + fun foo(s: String = "O") = s + return foo() + foo("K") +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/ir/AbstractComposeLikeIrBlackBoxCodegenTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/ir/AbstractComposeLikeIrBlackBoxCodegenTest.kt new file mode 100644 index 00000000000..06ba3520954 --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/ir/AbstractComposeLikeIrBlackBoxCodegenTest.kt @@ -0,0 +1,336 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen.ir + +import com.intellij.mock.MockProject +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.AbstractBlackBoxCodegenTest +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl +import org.jetbrains.kotlin.ir.descriptors.WrappedValueParameterDescriptor +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrValueSymbol +import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.isPrimitiveType +import org.jetbrains.kotlin.ir.types.makeNullable +import org.jetbrains.kotlin.ir.util.hasDefaultValue +import org.jetbrains.kotlin.ir.util.isInlined +import org.jetbrains.kotlin.ir.util.statements +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.test.TargetBackend + +abstract class AbstractComposeLikeIrBlackBoxCodegenTest : AbstractBlackBoxCodegenTest() { + override val backend = TargetBackend.JVM_IR + + override fun setupEnvironment(environment: KotlinCoreEnvironment) { + ComposeLikeExtensionRegistrar.registerComponents(environment.project) + super.setupEnvironment(environment) + } + +} + +class ComposeLikeExtensionRegistrar : ComponentRegistrar { + companion object { + fun registerComponents(project: Project) { + IrGenerationExtension.registerExtension(project, ComposeLikeGenerationExtension()) + } + } + + override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { + registerComponents(project) + } +} + +class ComposeLikeGenerationExtension : IrGenerationExtension { + private val rewrittenFunctions = mutableSetOf() + + override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { + moduleFragment.transformChildrenVoid(ComposeLikeDefaultArgumentRewriter(pluginContext, rewrittenFunctions)) + moduleFragment.transformChildrenVoid(ComposeLikeDefaultMethodCallRewriter(pluginContext, rewrittenFunctions)) + } +} + +class ComposeLikeDefaultMethodCallRewriter(private val context: IrPluginContext, private val rewrittenFunctions: Set) : + IrElementTransformerVoid() { + override fun visitCall(expression: IrCall): IrExpression { + val function = expression.symbol.owner + return if (rewrittenFunctions.contains(function)) { + IrCallImpl( + expression.startOffset, + expression.endOffset, + expression.type, + expression.symbol, + function.typeParameters.size, + function.valueParameters.size, + expression.origin, + expression.superQualifierSymbol + ).also { + it.dispatchReceiver = expression.dispatchReceiver?.transform(this, null) + it.extensionReceiver = expression.extensionReceiver?.transform(this, null) + var bitmap = 0 + for (i in function.valueParameters.indices) { + if (i < expression.valueArgumentsCount) { + if (expression.getValueArgument(i) != null) { + it.putValueArgument(i, expression.getValueArgument(i)) + } else { + bitmap = bitmap or (1.shl(i)) + it.putValueArgument( + i, + IrConstImpl.defaultValueForType( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + function.valueParameters[i].type + ).let { defaultValue -> + IrCompositeImpl( + defaultValue.startOffset, + defaultValue.endOffset, + defaultValue.type, + IrStatementOrigin.DEFAULT_VALUE, + listOf(defaultValue) + ) + } + ) + } + } + } + it.putValueArgument( + function.valueParameters.size - 1, + IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.intType, bitmap) + ) + } + } else { + super.visitCall(expression) + } + } +} + +class ComposeLikeDefaultArgumentRewriter( + private val context: IrPluginContext, + private val rewrittenFunctions: MutableSet +) : IrElementTransformerVoid() { + private val parameterMapping = mutableMapOf() + + override fun visitGetValue(expression: IrGetValue): IrExpression { + parameterMapping[expression.symbol.owner]?.let { + return IrGetValueImpl( + expression.startOffset, + expression.endOffset, + expression.type, + it.symbol, + expression.origin + ) + } ?: return super.visitGetValue(expression) + } + + override fun visitFunction(declaration: IrFunction): IrStatement { + val hasDefaultArguments = declaration.valueParameters.any { it.defaultValue != null } + if (!hasDefaultArguments) return super.visitFunction(declaration) + rewrittenFunctions.add(declaration) + val newParameters = mutableListOf() + declaration.valueParameters.forEach { param -> + newParameters.add( + if (param.defaultValue != null) { + val result = IrValueParameterImpl( + param.startOffset, + param.endOffset, + param.origin, + IrValueParameterSymbolImpl(WrappedValueParameterDescriptor()), + param.name, + index = param.index, + type = defaultParameterType(param), + varargElementType = param.varargElementType, + isCrossinline = param.isCrossinline, + isNoinline = param.isNoinline, + isAssignable = param.defaultValue != null + ).also { + it.defaultValue = param.defaultValue + it.parent = declaration + } + parameterMapping[param] = result + result + } else param + ) + } + declaration.valueParameters = newParameters + val defaultParam = declaration.addValueParameter( + "\$default", + context.irBuiltIns.intType, + IrDeclarationOrigin.MASK_FOR_DEFAULT_FUNCTION + ) + declaration.transformChildrenVoid() + val body = declaration.body!! + val defaultSelection = mutableListOf() + declaration.valueParameters.forEach { + if (it.hasDefaultValue()) { + val index = defaultSelection.size + defaultSelection.add( + irIf( + condition = irGetBit(defaultParam, index), + body = irSet(it, it.defaultValue!!.expression) + ) + ) + it.defaultValue = null + } + } + + declaration.body = IrBlockBodyImpl( + body.startOffset, + body.endOffset, + listOf( + *defaultSelection.toTypedArray(), + *body.statements.toTypedArray() + ), + ) + return declaration + } + + private fun defaultParameterType(param: IrValueParameter): IrType { + val type = param.type + return when { + type.isPrimitiveType() -> type + type.isInlined() -> type + else -> type.makeNullable() + } + } + + private fun irIf(condition: IrExpression, body: IrExpression): IrExpression { + return IrIfThenElseImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.irBuiltIns.unitType, + origin = IrStatementOrigin.IF + ).also { + it.branches.add( + IrBranchImpl(condition, body) + ) + } + } + + private fun irSet(variable: IrValueDeclaration, value: IrExpression): IrExpression { + return IrSetValueImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.irBuiltIns.unitType, + variable.symbol, + value = value, + origin = null + ) + } + + private fun irNotEqual(lhs: IrExpression, rhs: IrExpression): IrExpression { + return irNot(irEqual(lhs, rhs)) + } + + private fun irGetBit(param: IrValueParameter, index: Int): IrExpression { + // value and (1 shl index) != 0 + return irNotEqual( + irAnd( + // a value of 1 in default means it was NOT provided + irGet(param), + irConst(0b1 shl index) + ), + irConst(0) + ) + } + + private fun irConst(value: Int): IrConst = IrConstImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.irBuiltIns.intType, + IrConstKind.Int, + value + ) + + private fun irConst(value: Boolean) = IrConstImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + context.irBuiltIns.booleanType, + IrConstKind.Boolean, + value + ) + + private fun irGet(type: IrType, symbol: IrValueSymbol): IrExpression { + return IrGetValueImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + type, + symbol + ) + } + + private fun irGet(variable: IrValueDeclaration): IrExpression { + return irGet(variable.type, variable.symbol) + } + + private fun IrType.binaryOperator(name: Name, paramType: IrType): IrFunctionSymbol = + context.symbols.getBinaryOperator(name, this, paramType) + + private fun irAnd(lhs: IrExpression, rhs: IrExpression): IrCallImpl { + return irCall( + lhs.type.binaryOperator(Name.identifier("and"), rhs.type), + null, + lhs, + null, + rhs + ) + } + + private fun irCall( + symbol: IrFunctionSymbol, + origin: IrStatementOrigin? = null, + dispatchReceiver: IrExpression? = null, + extensionReceiver: IrExpression? = null, + vararg args: IrExpression + ): IrCallImpl { + return IrCallImpl( + UNDEFINED_OFFSET, + UNDEFINED_OFFSET, + symbol.owner.returnType, + symbol as IrSimpleFunctionSymbol, + symbol.owner.typeParameters.size, + symbol.owner.valueParameters.size, + origin + ).also { + if (dispatchReceiver != null) it.dispatchReceiver = dispatchReceiver + if (extensionReceiver != null) it.extensionReceiver = extensionReceiver + args.forEachIndexed { index, arg -> + it.putValueArgument(index, arg) + } + } + } + + private fun irNot(value: IrExpression): IrExpression { + return irCall( + context.irBuiltIns.booleanNotSymbol, + dispatchReceiver = value + ) + } + + private fun irEqual(lhs: IrExpression, rhs: IrExpression): IrExpression { + return irCall( + context.irBuiltIns.eqeqeqSymbol, + null, + null, + null, + lhs, + rhs + ) + } +} diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/ComposeLikeIrBlackBoxCodegenTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/ComposeLikeIrBlackBoxCodegenTestGenerated.java new file mode 100644 index 00000000000..3a3a08e1fe1 --- /dev/null +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/ir/ComposeLikeIrBlackBoxCodegenTestGenerated.java @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen.ir; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/testData/codegen/composeLike") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ComposeLikeIrBlackBoxCodegenTestGenerated extends AbstractComposeLikeIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInComposeLike() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/composeLike"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @TestMetadata("default.kt") + public void testDefault() throws Exception { + runTest("compiler/testData/codegen/composeLike/default.kt"); + } + + @TestMetadata("defaultInline.kt") + public void testDefaultInline() throws Exception { + runTest("compiler/testData/codegen/composeLike/defaultInline.kt"); + } + + @TestMetadata("defaultLocal.kt") + public void testDefaultLocal() throws Exception { + runTest("compiler/testData/codegen/composeLike/defaultLocal.kt"); + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt index 69e9121d2b7..b7230c63cef 100644 --- a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt +++ b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt @@ -251,8 +251,6 @@ fun main(args: Array) { model("ir/sourceRanges") } - - testClass { model("codegen/bytecodeListing", targetBackend = TargetBackend.JVM) } @@ -462,6 +460,10 @@ fun main(args: Array) { model("lexer/kotlin") } + testClass { + model("codegen/composeLike", targetBackend = TargetBackend.JVM_IR) + } + testClass { model("codegen/box", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions")) }