[JVM_IR] Add tests of Compose-like default argument handling.

This commit is contained in:
Mads Ager
2020-11-30 16:41:07 +01:00
committed by Alexander Udalov
parent a7efa5c98b
commit 83588e9f22
6 changed files with 397 additions and 2 deletions
+4
View File
@@ -0,0 +1,4 @@
fun foo(s: String = "O") = s
fun box() = foo() + foo("K")
@@ -0,0 +1,3 @@
inline fun foo(s: String = "O") = s
fun box() = foo() + foo("K")
+4
View File
@@ -0,0 +1,4 @@
fun box(): String {
fun foo(s: String = "O") = s
return foo() + foo("K")
}
@@ -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<IrFunction>()
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<IrFunction>) :
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<IrFunction>
) : IrElementTransformerVoid() {
private val parameterMapping = mutableMapOf<IrValueParameter, IrValueParameter>()
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<IrValueParameter>()
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<IrStatement>()
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<Int> = 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
)
}
}
@@ -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");
}
}
@@ -251,8 +251,6 @@ fun main(args: Array<String>) {
model("ir/sourceRanges")
}
testClass<AbstractBytecodeListingTest> {
model("codegen/bytecodeListing", targetBackend = TargetBackend.JVM)
}
@@ -462,6 +460,10 @@ fun main(args: Array<String>) {
model("lexer/kotlin")
}
testClass<AbstractComposeLikeIrBlackBoxCodegenTest> {
model("codegen/composeLike", targetBackend = TargetBackend.JVM_IR)
}
testClass<AbstractIrBlackBoxCodegenTest> {
model("codegen/box", targetBackend = TargetBackend.JVM_IR, excludeDirs = listOf("oldLanguageVersions"))
}