From cb4e5399d41d057fbe491211d6052c4b8c7d8b53 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 12 Feb 2018 13:27:07 +0300 Subject: [PATCH] Support default arguments in expect declarations --- .../konan/lower/ExpectDeclarationsRemoving.kt | 112 +++++++++++++++++- backend.native/tests/build.gradle | 5 + .../tests/codegen/mpp/mpp_default_args.kt | 63 ++++++++++ 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 backend.native/tests/codegen/mpp/mpp_default_args.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ExpectDeclarationsRemoving.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ExpectDeclarationsRemoving.kt index 12ea32ab968..def7687e371 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ExpectDeclarationsRemoving.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/ExpectDeclarationsRemoving.kt @@ -3,7 +3,23 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.descriptors.isExpectMember -import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.MemberDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl +import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol +import org.jetbrains.kotlin.ir.symbols.IrValueSymbol +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid +import org.jetbrains.kotlin.resolve.descriptorUtil.module +import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver /** * This pass removes all declarations with `isExpect == true`. @@ -12,6 +28,98 @@ internal class ExpectDeclarationsRemoving(val context: Context) : FileLoweringPa override fun lower(irFile: IrFile) { // All declarations with `isExpect == true` are nested into a top-level declaration with `isExpect == true`. - irFile.declarations.removeAll { it.descriptor.isExpectMember } + irFile.declarations.removeAll { + if (it.descriptor.isExpectMember) { + copyDefaultArgumentsFromExpectToActual(it) + true + } else { + false + } + } + } + + private fun copyDefaultArgumentsFromExpectToActual(declaration: IrDeclaration) { + declaration.acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitValueParameter(declaration: IrValueParameter) { + super.visitValueParameter(declaration) + + val defaultValue = declaration.defaultValue ?: return + val function = declaration.parent as IrFunction + + val index = declaration.index + assert(function.valueParameters[index] == declaration) + + function.findActualForExpected().valueParameters[index].defaultValue = defaultValue.also { + it.expression = it.expression.remapExpectValueSymbols() + } + } + }) + } + + private fun IrFunction.findActualForExpected(): IrFunction = + context.ir.symbols.symbolTable.referenceFunction(descriptor.findActualForExpect()).owner as IrFunction + + private fun IrClass.findActualForExpected(): IrClass = + context.ir.symbols.symbolTable.referenceClass(descriptor.findActualForExpect()).owner + + private inline fun T.findActualForExpect() = with(ExpectedActualResolver) { + val descriptor = this@findActualForExpect + + if (!descriptor.isExpect) error(this) + + findCompatibleActualForExpected(descriptor.module).singleOrNull() ?: error(descriptor) + } as T + + private fun IrExpression.remapExpectValueSymbols(): IrExpression { + return this.transform(object : IrElementTransformerVoid() { + + override fun visitGetValue(expression: IrGetValue): IrExpression { + expression.transformChildrenVoid() + val newSymbol = remapExpectValueSymbol(expression.symbol) + ?: return expression + + return IrGetValueImpl( + expression.startOffset, + expression.endOffset, + newSymbol, + expression.origin + ) + } + }, data = null) + } + + private fun remapExpectValueSymbol(symbol: IrValueSymbol): IrValueParameterSymbol? { + if (symbol !is IrValueParameterSymbol) { + return null + } + + val parameter = symbol.owner + val parent = parameter.parent + + return when (parent) { + is IrClass -> { + assert(parameter == parent.thisReceiver) + parent.findActualForExpected().thisReceiver!! + } + + is IrFunction -> when { + parameter == parent.dispatchReceiverParameter -> + parent.findActualForExpected().dispatchReceiverParameter!! + + parameter == parent.extensionReceiverParameter -> + parent.findActualForExpected().extensionReceiverParameter!! + + else -> { + assert(parent.valueParameters[parameter.index] == parameter) + parent.findActualForExpected().valueParameters[parameter.index] + } + } + + else -> error(parent) + }.symbol } } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 4c3e646106b..3ed87ca9c69 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2061,6 +2061,11 @@ task mpp2(type: LinkKonanTest) { flags = ['-Xmulti-platform'] } +task mpp_default_args(type: RunStandaloneKonanTest) { + source = "codegen/mpp/mpp_default_args.kt" + flags = ['-tr', '-Xmulti-platform'] +} + task unit1(type: RunKonanTest) { goldValue = "First\nkotlin.Unit\n" source = "codegen/basics/unit1.kt" diff --git a/backend.native/tests/codegen/mpp/mpp_default_args.kt b/backend.native/tests/codegen/mpp/mpp_default_args.kt new file mode 100644 index 00000000000..84860991a4c --- /dev/null +++ b/backend.native/tests/codegen/mpp/mpp_default_args.kt @@ -0,0 +1,63 @@ +package codegen.mpp.mpp_default_args + +import kotlin.test.* + +@Test fun runTest() { + box() +} + +fun box() { + assertEquals(test1(), 42) + assertEquals(test2(17), 34) + assertEquals(test3(), -1) + + Test4().test() + Test5().Inner().test() + + 42.test6() +} + +expect fun test1(x: Int = 42): Int +actual fun test1(x: Int) = x + +expect fun test2(x: Int, y: Int = x): Int +actual fun test2(x: Int, y: Int) = x + y + +expect fun test3(x: Int = 42, y: Int = x + 1): Int +actual fun test3(x: Int, y: Int) = x - y + +expect class Test4 { + fun test(arg: Any = this) +} + +actual class Test4 { + actual fun test(arg: Any) { + assertEquals(arg, this) + } +} + +expect class Test5 { + inner class Inner { + constructor(arg: Any = this@Test5) + + fun test(arg1: Any = this@Test5, arg2: Any = this@Inner) + } +} + +actual class Test5 { + actual inner class Inner { + actual constructor(arg: Any) { + assertEquals(arg, this@Test5) + } + + actual fun test(arg1: Any, arg2: Any) { + assertEquals(arg1, this@Test5) + assertEquals(arg2, this@Inner) + } + } +} + +expect fun Int.test6(arg: Int = this) +actual fun Int.test6(arg: Int) { + assertEquals(arg, this) +}