diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/AbstractValueUsageTransformer.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/AbstractValueUsageTransformer.kt new file mode 100644 index 00000000000..652509b9534 --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/AbstractValueUsageTransformer.kt @@ -0,0 +1,284 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.backend.common.lower + +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrStringConcatenationImpl +import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol +import org.jetbrains.kotlin.ir.symbols.IrReturnTargetSymbol +import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + + +/** + * Transforms expressions depending on the context they are used in. + * + * The transformations are defined with `IrExpression.use*` methods in this class, + * the most common are [useAs], [useAsStatement], [useInTypeOperator]. + * + * NOTE: Transformer is copied from Kotlin/Native with minor modifications + * + * TODO: the implementation is originally based on [org.jetbrains.kotlin.psi2ir.transformations.InsertImplicitCasts] + * and should probably be used as its base. + * + * TODO: consider making this visitor non-recursive to make it more general. + */ +abstract class AbstractValueUsageTransformer( + protected val irBuiltIns: IrBuiltIns +) : IrElementTransformerVoid() { + + protected open fun IrExpression.useAs(type: IrType): IrExpression = this + + protected open fun IrExpression.useAsStatement(): IrExpression = this + + protected open fun IrExpression.useInTypeOperator(operator: IrTypeOperator, typeOperand: IrType): IrExpression = + this + + protected open fun IrExpression.useAsValue(value: IrValueDeclaration): IrExpression = this.useAs(value.type) + + protected open fun IrExpression.useAsArgument(parameter: IrValueParameter): IrExpression = + this.useAsValue(parameter) + + protected open fun IrExpression.useAsDispatchReceiver(expression: IrFunctionAccessExpression): IrExpression = + this.useAsArgument(expression.symbol.owner.dispatchReceiverParameter!!) + + protected open fun IrExpression.useAsExtensionReceiver(expression: IrFunctionAccessExpression): IrExpression = + this.useAsArgument(expression.symbol.owner.extensionReceiverParameter!!) + + protected open fun IrExpression.useAsValueArgument( + expression: IrFunctionAccessExpression, + parameter: IrValueParameter + ): IrExpression = + this.useAsArgument(parameter) + + private fun IrExpression.useForVariable(variable: IrVariable): IrExpression = + this.useAsValue(variable) + + private fun IrExpression.useForField(field: IrField): IrExpression = + this.useAs(field.type) + + protected open fun IrExpression.useAsReturnValue(returnTarget: IrReturnTargetSymbol): IrExpression = + when (returnTarget) { + is IrSimpleFunctionSymbol -> this.useAs(returnTarget.owner.returnType) + is IrConstructorSymbol -> this.useAs(irBuiltIns.unitType) + is IrReturnableBlockSymbol -> this.useAs(returnTarget.owner.type) + else -> error(returnTarget) + } + + protected open fun IrExpression.useAsResult(enclosing: IrExpression): IrExpression = + this.useAs(enclosing.type) + + protected open fun IrExpression.useAsVarargElement(expression: IrVararg): IrExpression = this + + override fun visitPropertyReference(expression: IrPropertyReference): IrExpression { + TODO() + } + + override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference): IrExpression { + TODO() + } + + // override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { + // TODO() + // } + + override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression { + expression.transformChildrenVoid(this) + + with(expression) { + dispatchReceiver = dispatchReceiver?.useAsDispatchReceiver(expression) + extensionReceiver = extensionReceiver?.useAsExtensionReceiver(expression) + for (index in descriptor.valueParameters.indices) { + val argument = getValueArgument(index) ?: continue + val parameter = symbol.owner.valueParameters[index] + putValueArgument(index, argument.useAsValueArgument(expression, parameter)) + } + } + + return expression + } + + override fun visitBlockBody(body: IrBlockBody): IrBody { + body.transformChildrenVoid(this) + + body.statements.forEachIndexed { i, irStatement -> + if (irStatement is IrExpression) { + body.statements[i] = irStatement.useAsStatement() + } + } + + return body + } + + override fun visitContainerExpression(expression: IrContainerExpression): IrExpression { + expression.transformChildrenVoid(this) + + if (expression.statements.isEmpty()) { + return expression + } + + val lastIndex = expression.statements.lastIndex + expression.statements.forEachIndexed { i, irStatement -> + if (irStatement is IrExpression) { + expression.statements[i] = + if (i == lastIndex) + irStatement.useAsResult(expression) + else + irStatement.useAsStatement() + } + } + + return expression + } + + override fun visitReturn(expression: IrReturn): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useAsReturnValue(expression.returnTargetSymbol) + + return expression + } + + override fun visitSetVariable(expression: IrSetVariable): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useForVariable(expression.symbol.owner) + + return expression + } + + override fun visitSetField(expression: IrSetField): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useForField(expression.symbol.owner) + + return expression + } + + override fun visitField(declaration: IrField): IrStatement { + declaration.transformChildrenVoid(this) + + declaration.initializer?.let { + it.expression = it.expression.useForField(declaration) + } + + return declaration + } + + override fun visitVariable(declaration: IrVariable): IrVariable { + declaration.transformChildrenVoid(this) + + declaration.initializer = declaration.initializer?.useForVariable(declaration) + + return declaration + } + + override fun visitWhen(expression: IrWhen): IrExpression { + expression.transformChildrenVoid(this) + + for (irBranch in expression.branches) { + irBranch.condition = irBranch.condition.useAs(irBuiltIns.booleanType) + irBranch.result = irBranch.result.useAsResult(expression) + } + + return expression + } + + override fun visitLoop(loop: IrLoop): IrExpression { + loop.transformChildrenVoid(this) + + loop.condition = loop.condition.useAs(irBuiltIns.booleanType) + + loop.body = loop.body?.useAsStatement() + + return loop + } + + override fun visitThrow(expression: IrThrow): IrExpression { + expression.transformChildrenVoid(this) + + expression.value = expression.value.useAs(irBuiltIns.throwableType) + + return expression + } + + override fun visitTry(aTry: IrTry): IrExpression { + aTry.transformChildrenVoid(this) + + aTry.tryResult = aTry.tryResult.useAsResult(aTry) + + for (aCatch in aTry.catches) { + aCatch.result = aCatch.result.useAsResult(aTry) + } + + aTry.finallyExpression = aTry.finallyExpression?.useAsStatement() + + return aTry + } + + override fun visitVararg(expression: IrVararg): IrExpression { + expression.transformChildrenVoid(this) + + expression.elements.forEachIndexed { i, element -> + when (element) { + is IrSpreadElement -> + element.expression = element.expression.useAs(expression.type) + is IrExpression -> { + expression.putElement(i, element.useAsVarargElement(expression)) + } + } + } + + return expression + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { + expression.transformChildrenVoid(this) + + expression.argument = expression.argument.useInTypeOperator(expression.operator, expression.typeOperand) + + return expression + } + + override fun visitFunction(declaration: IrFunction): IrStatement { + declaration.transformChildrenVoid(this) + + declaration.valueParameters.forEach { parameter -> + val defaultValue = parameter.defaultValue + if (defaultValue is IrExpressionBody) { + defaultValue.expression = defaultValue.expression.useAsArgument(parameter) + } + } + + declaration.body?.let { + if (it is IrExpressionBody) { + it.expression = it.expression.useAsReturnValue(declaration.symbol) + } + } + + return declaration + } + + override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression { + expression.transformChildrenVoid() + if (expression is IrStringConcatenationImpl) { + for ((i, arg) in expression.arguments.withIndex()) { + expression.arguments[i] = arg.useAs(irBuiltIns.anyNType) + } + } + return expression + } + + // TODO: IrEnumEntry? + +} + diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InlineClassDeclarationLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InlineClassDeclarationLowering.kt new file mode 100644 index 00000000000..c6a10ebc4ba --- /dev/null +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/InlineClassDeclarationLowering.kt @@ -0,0 +1,263 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.backend.common.lower + +import org.jetbrains.kotlin.backend.common.BackendContext +import org.jetbrains.kotlin.backend.common.ClassLoweringPass +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor +import org.jetbrains.kotlin.backend.common.ir.copyTo +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name + +private const val INLINE_CLASS_IMPL_SUFFIX = "-impl" + +// TODO: Support incremental compilation +class InlineClassLowering(val context: BackendContext) { + private val transformedFunction = mutableMapOf() + + val inlineClassDeclarationLowering = object : ClassLoweringPass { + override fun lower(irClass: IrClass) { + if (!irClass.isInline) return + + irClass.transformDeclarationsFlat { declaration -> + when (declaration) { + is IrConstructor -> listOf(transformConstructor(declaration)) + is IrSimpleFunction -> transformMethodFlat(declaration) + is IrProperty -> listOf(declaration) // Getters and setters should be flattened + is IrField -> listOf(declaration) + is IrClass -> listOf(declaration) + else -> error("Unexpected declaration: $declaration") + } + } + } + + private fun transformConstructor(irConstructor: IrConstructor): IrDeclaration { + if (irConstructor.isPrimary) return irConstructor + + // Secondary constructors are lowered into static function + val result = transformedFunction.getOrPut(irConstructor.symbol) { createStaticBodilessMethod(irConstructor).symbol }.owner + val irClass = irConstructor.parentAsClass + + // Copied and adapted from Kotlin/Native InlineClassTransformer + result.body = context.createIrBuilder(result.symbol).irBlockBody(result) { + + // Secondary ctors of inline class must delegate to some other constructors. + // Use these delegating call later to initialize this variable. + lateinit var thisVar: IrVariable + val parameterMapping = result.valueParameters.associateBy { it -> + irConstructor.valueParameters[it.index].symbol + } + + (irConstructor.body as IrBlockBody).statements.forEach { statement -> + +statement.transform(object : IrElementTransformerVoid() { + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { + expression.transformChildrenVoid() + return irBlock(expression) { + thisVar = irTemporary( + expression, + typeHint = irClass.defaultType.toKotlinType(), + irType = irClass.defaultType + ) + } + } + + override fun visitGetValue(expression: IrGetValue): IrExpression { + expression.transformChildrenVoid() + if (expression.symbol == irClass.thisReceiver?.symbol) { + return irGet(thisVar) + } + + parameterMapping[expression.symbol]?.let { return irGet(it) } + return expression + } + + override fun visitReturn(expression: IrReturn): IrExpression { + expression.transformChildrenVoid() + if (expression.returnTargetSymbol == irConstructor.symbol) { + return irReturn(irBlock(expression.startOffset, expression.endOffset) { + +expression.value + +irGet(thisVar) + }) + } + + return expression + } + + }, null) + } + +irReturn(irGet(thisVar)) + } + + return result + } + + + private fun transformMethodFlat(function: IrSimpleFunction): List { + // TODO: Support fake-overridden methods without boxing + if (function.isStaticMethodOfClass || !function.isReal) + return listOf(function) + + val staticMethod = createStaticBodilessMethod(function) + transformedFunction[function.symbol] = staticMethod.symbol + + // Move function body to static method, transforming value parameters and nested declarations + function.body!!.transformChildrenVoid(object : IrElementTransformerVoid() { + override fun visitDeclaration(declaration: IrDeclaration): IrStatement { + declaration.transformChildrenVoid(this) + + // TODO: Variable parents might not be initialized + if (declaration !is IrVariable && declaration.parent == function) + declaration.parent = staticMethod + + return declaration + } + + override fun visitGetValue(expression: IrGetValue): IrExpression { + val valueDeclaration = expression.symbol.owner as? IrValueParameter ?: return super.visitGetValue(expression) + + return context.createIrBuilder(staticMethod.symbol).irGet( + when (valueDeclaration) { + function.dispatchReceiverParameter, function.parentAsClass.thisReceiver -> + staticMethod.valueParameters[0] + + function.extensionReceiverParameter -> + staticMethod.extensionReceiverParameter!! + + in function.valueParameters -> + staticMethod.valueParameters[valueDeclaration.index + 1] + + else -> return expression + } + ) + } + }) + + staticMethod.body = function.body + + if (function.overriddenSymbols.isEmpty()) // Function is used only in unboxed context + return listOf(staticMethod) + + // Delegate original function to static implementation + function.body = context.createIrBuilder(function.symbol).irBlockBody { + +irReturn( + irCall(staticMethod).apply { + val parameters = + listOf(function.dispatchReceiverParameter!!) + function.valueParameters + + for ((index, valueParameter) in parameters.withIndex()) { + putValueArgument(index, irGet(valueParameter)) + } + + extensionReceiver = function.extensionReceiverParameter?.let { irGet(it) } + } + ) + } + + return listOf(function, staticMethod) + } + } + + val inlineClassUsageLowering = object : FileLoweringPass { + + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + + override fun visitCall(call: IrCall): IrExpression { + call.transformChildrenVoid(this) + val function = call.symbol.owner + if ( + function.isDynamic() || + function.parent !is IrClass || + function.isStaticMethodOfClass || + !function.parentAsClass.isInline || + (function is IrSimpleFunction && !function.isReal) || + (function is IrConstructor && function.isPrimary) + ) { + return call + } + + return irCall(call, getOrCreateStaticMethod(function), dispatchReceiverAsFirstArgument = (function is IrSimpleFunction)) + } + + override fun visitDelegatingConstructorCall(call: IrDelegatingConstructorCall): IrExpression { + call.transformChildrenVoid(this) + val function = call.symbol.owner + val klass = function.parentAsClass + return when { + !klass.isInline -> call + function.isPrimary -> irCall(call, function) + else -> irCall(call, getOrCreateStaticMethod(function)).apply { + (0 until call.valueArgumentsCount).forEach { + putValueArgument(it, call.getValueArgument(it)!!) + } + } + } + } + + private fun getOrCreateStaticMethod(function: IrFunction): IrSimpleFunctionSymbol = + transformedFunction.getOrPut(function.symbol) { + createStaticBodilessMethod(function).also { + function.parentAsClass.declarations.add(it) + }.symbol + } + }) + } + } + + private fun Name.toInlineClassImplementationName() = when { + isSpecial -> Name.special(asString() + INLINE_CLASS_IMPL_SUFFIX) + else -> Name.identifier(asString() + INLINE_CLASS_IMPL_SUFFIX) + } + + private fun createStaticBodilessMethod(function: IrFunction): IrSimpleFunction { + val descriptor = WrappedSimpleFunctionDescriptor() + return IrFunctionImpl( + function.startOffset, + function.endOffset, + function.origin, + IrSimpleFunctionSymbolImpl(descriptor), + function.name.toInlineClassImplementationName(), + function.visibility, + Modality.FINAL, + function.isInline, + function.isExternal, + (function is IrSimpleFunction && function.isTailrec), + (function is IrSimpleFunction && function.isSuspend) + ).apply { + descriptor.bind(this) + returnType = when (function) { + is IrSimpleFunction -> function.returnType + is IrConstructor -> function.parentAsClass.defaultType + else -> error("Unknown function type") + } + typeParameters += function.typeParameters + annotations += function.annotations + dispatchReceiverParameter = null + extensionReceiverParameter = function.extensionReceiverParameter?.copyTo(this) + if (function is IrSimpleFunction) { + valueParameters.add(function.dispatchReceiverParameter!!.copyTo(this, shift = 1)) + valueParameters += function.valueParameters.map { p -> p.copyTo(this, shift = 1) } + } else { + valueParameters += function.valueParameters.map { p -> p.copyTo(this, shift = 0) } + } + parent = function.parent + assert(isStaticMethodOfClass) + } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index d9d2cc411e6..ebce0855f38 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -250,6 +250,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val jsCharSequenceLength = getInternalFunction("charSequenceLength") val jsCharSequenceSubSequence = getInternalFunction("charSequenceSubSequence") + val jsBoxIntrinsic = getInternalFunction("boxIntrinsic") + val jsUnboxIntrinsic = getInternalFunction("unboxIntrinsic") + // Helpers: private fun getInternalFunction(name: String) = diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index abe3f19b91e..3a3532b5cf2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project import org.jetbrains.kotlin.backend.common.* import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension import org.jetbrains.kotlin.backend.common.lower.* +import org.jetbrains.kotlin.backend.common.lower.InlineClassLowering import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -130,6 +131,11 @@ private fun JsIrBackendContext.lower(moduleFragment: IrModuleFragment, dependenc constructorRedirectorLowering.runOnFilesPostfix(moduleFragment) } + InlineClassLowering(this).apply { + inlineClassDeclarationLowering.runOnFilesPostfix(moduleFragment) + inlineClassUsageLowering.lower(moduleFragment) + } + AutoboxingTransformer(this).lower(moduleFragment) ClassReferenceLowering(this).lower(moduleFragment) PrimitiveCompanionLowering(this).lower(moduleFragment) @@ -152,4 +158,4 @@ private fun FunctionLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragm moduleFragment.files.forEach { runOnFilePostfix(it) } private fun ClassLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) = - moduleFragment.files.forEach { runOnFilePostfix(it) } \ No newline at end of file + moduleFragment.files.forEach { runOnFilePostfix(it) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt new file mode 100644 index 00000000000..ca3e4634623 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/AutoboxingTransformer.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.ir.backend.js.lower + +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.lower.AbstractValueUsageTransformer +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.isNothing +import org.jetbrains.kotlin.ir.types.makeNotNull +import org.jetbrains.kotlin.ir.util.getInlinedClass +import org.jetbrains.kotlin.ir.util.isInlined + + +// Copied and adapted from Kotlin/Native + +class AutoboxingTransformer(val context: JsIrBackendContext) : AbstractValueUsageTransformer(context.irBuiltIns), FileLoweringPass { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid() + } + + override fun IrExpression.useAs(type: IrType): IrExpression { + + val actualType = when (this) { + is IrCall -> { + if (this.symbol.owner.let { it is IrSimpleFunction && it.isSuspend }) { + irBuiltIns.anyNType + } else { + try { + this.symbol.owner.returnType + } catch (e: kotlin.UninitializedPropertyAccessException) { + // TODO: Fix lateinit return types + this.type + } + } + } + is IrGetField -> this.symbol.owner.type + + is IrTypeOperatorCall -> when (this.operator) { + IrTypeOperator.IMPLICIT_INTEGER_COERCION -> + // TODO: is it a workaround for inconsistent IR? + this.typeOperand + + IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST -> context.irBuiltIns.anyNType + + else -> this.type + } + + is IrGetValue -> { + val value = this.symbol.owner + if (value is IrValueParameter && value.isDispatchReceiver) { + irBuiltIns.anyNType + } else { + this.type + } + } + + else -> this.type + } + + // TODO: Default parameters are passed as nulls and they need not to be unboxed. Fix this + if (actualType.makeNotNull().isNothing()) + return this + + val expectedType = type + + val actualInlinedClass = actualType.getInlinedClass() + val expectedInlinedClass = expectedType.getInlinedClass() + + val function = when { + actualInlinedClass == null && expectedInlinedClass == null -> return this + actualInlinedClass != null && expectedInlinedClass == null -> context.intrinsics.jsBoxIntrinsic + actualInlinedClass == null && expectedInlinedClass != null -> context.intrinsics.jsUnboxIntrinsic + else -> return this + } + + return JsIrBuilder.buildCall( + function, + expectedType, + typeArguments = listOf(actualType, expectedType) + ).also { + it.putValueArgument(0, this) + } + } + + override fun IrExpression.useAsVarargElement(expression: IrVararg): IrExpression { + return this.useAs( + if (this.type.isInlined()) + irBuiltIns.anyNType + else + expression.varargElementType + ) + } + + private val IrValueParameter.isDispatchReceiver: Boolean + get() { + val parent = this.parent + if (parent is IrClass) + return true + if (parent is IrFunction && parent.dispatchReceiverParameter == this) + return true + return false + } + +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CallableReferenceLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CallableReferenceLowering.kt index bacdf4bc601..83acadcb24f 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CallableReferenceLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CallableReferenceLowering.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.ir.types.IrSimpleType import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrTypeProjection import org.jetbrains.kotlin.ir.types.toIrType +import org.jetbrains.kotlin.ir.util.isInlined import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -357,6 +358,12 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP return returnStatements } + private fun IrType.boxIfInlined() = if (isInlined()) { + context.irBuiltIns.anyNType + } else { + this + } + private fun generateSignatureForClosure( callable: IrFunction, factory: IrFunction, @@ -387,7 +394,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP callable.dispatchReceiverParameter?.let { dispatch -> if (reference.dispatchReceiver == null) { - result.add(JsIrBuilder.buildValueParameter(dispatch.name, result.size, dispatch.type).also { it.parent = closure }) + result.add(JsIrBuilder.buildValueParameter(dispatch.name, result.size, dispatch.type.boxIfInlined()).also { it.parent = closure }) } else { // do not add dispatch receiver in result signature if it is bound capturedParams-- @@ -396,7 +403,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP callable.extensionReceiverParameter?.let { ext -> if (reference.extensionReceiver == null) { - result.add(JsIrBuilder.buildValueParameter(ext.name, result.size, ext.type).also { it.parent = closure }) + result.add(JsIrBuilder.buildValueParameter(ext.name, result.size, ext.type.boxIfInlined()).also { it.parent = closure }) } else { // the same as for dispatch capturedParams-- @@ -406,7 +413,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP for ((index, param) in (result.size until arity).zip(callable.valueParameters.drop(capturedParams))) { val type = if (index < functionSignature.size) functionSignature[index] else param.type val paramName = param.name.run { if (!isSpecial) identifier else "p$index" } - result += JsIrBuilder.buildValueParameter(paramName, result.size, type).also { it.parent = closure } + result += JsIrBuilder.buildValueParameter(paramName, result.size, type.boxIfInlined()).also { it.parent = closure } } return result @@ -492,7 +499,7 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP val boundParamSymbols = factoryFunction.valueParameters.map { it.symbol } val unboundParamDeclarations = generateSignatureForClosure(declaration, factoryFunction, closureFunction, reference, arity) val unboundParamSymbols = unboundParamDeclarations.map { it.symbol } - val returnType = declaration.returnType + val returnType = declaration.returnType.boxIfInlined() closureFunction.valueParameters += unboundParamDeclarations closureFunction.returnType = returnType @@ -540,4 +547,4 @@ class CallableReferenceLowering(val context: JsIrBackendContext) : FileLoweringP return closureFunction } -} \ No newline at end of file +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt index d191e83163c..ee30515e2bc 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt @@ -38,14 +38,18 @@ class SecondaryCtorLowering(val context: JsIrBackendContext) { val constructorProcessorLowering = object : DeclarationContainerLoweringPass { override fun lower(irDeclarationContainer: IrDeclarationContainer) { - irDeclarationContainer.declarations.filterIsInstance().forEach { lowerClass(it) } + irDeclarationContainer.declarations.filterIsInstance().forEach { + if (!it.isInline) // Inline classes are lowered separately + lowerClass(it) + } } } val constructorRedirectorLowering = object : DeclarationContainerLoweringPass { override fun lower(irDeclarationContainer: IrDeclarationContainer) { if (irDeclarationContainer is IrClass) { - updateConstructorDeclarations(irDeclarationContainer) + if (!irDeclarationContainer.isInline) // Inline classes are lowered separately + updateConstructorDeclarations(irDeclarationContainer) } for (it in irDeclarationContainer.declarations) { it.accept(CallsiteRedirectionTransformer(), null) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 693a479daa7..e98dbf0ae89 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -8,12 +8,14 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.utils.Namer +import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrConstructor import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol import org.jetbrains.kotlin.ir.types.IrDynamicType import org.jetbrains.kotlin.ir.util.isFunctionTypeOrSubtype +import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.util.OperatorNameConventions @@ -63,6 +65,12 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer JsExpression @@ -183,6 +186,21 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) { JsNew(JsNameRef("${prefix}Array"), translateCallArguments(call, context)) } } + + add(intrinsics.jsBoxIntrinsic) { call: IrCall, context -> + val arg = translateCallArguments(call, context).single() + val inlineClass = call.getTypeArgument(0)!!.getInlinedClass()!! + val constructor = inlineClass.declarations.filterIsInstance().single { it.isPrimary } + JsNew(context.getNameForSymbol(constructor.symbol).makeRef(), listOf(arg)) + } + + add(intrinsics.jsUnboxIntrinsic) { call: IrCall, context -> + val arg = translateCallArguments(call, context).single() + val inlineClass = call.getTypeArgument(1)!!.getInlinedClass()!! + val field = getInlineClassBackingField(inlineClass) + val fieldName = context.getNameForSymbol(field.symbol) + JsNameRef(fieldName, arg) + } } } diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/InlineClasses.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/InlineClasses.kt new file mode 100644 index 00000000000..1d199a51fd3 --- /dev/null +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/InlineClasses.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.ir.util + +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.classifierOrFail +import org.jetbrains.kotlin.ir.types.isMarkedNullable + + +/** + * Returns inline class for given class or null of type is not inlined + * TODO: Make this configurable for different backends (currently implements logic of JS BE) + */ +fun IrType.getInlinedClass(): IrClass? { + if (this is IrSimpleType) { + val erased = erase(this) ?: return null + if (!this.isMarkedNullable() && erased.isInline) { + // TODO: Don't box nullable type inline classes with non-nullable underlying type (JS IR BE) + return erased + } + } + return null +} + +fun IrType.isInlined(): Boolean = this.getInlinedClass() != null + +private tailrec fun erase(type: IrType): IrClass? { + val classifier = type.classifierOrFail + + // TODO: Fix unbound symbols + if (!classifier.isBound) + return null + + return when (classifier) { + is IrClassSymbol -> classifier.owner + is IrTypeParameterSymbol -> erase(classifier.owner.superTypes.first()) + else -> error(classifier) + } +} + +fun getInlineClassBackingField(irClass: IrClass): IrField { + for (declaration in irClass.declarations) { + if (declaration is IrField) + return declaration + + if (declaration is IrProperty) + return declaration.backingField ?: continue + } + error("Inline class has no field") +} diff --git a/libraries/stdlib/js/irRuntime/core.kt b/libraries/stdlib/js/irRuntime/core.kt index 78e15a08609..1c3e85e9402 100644 --- a/libraries/stdlib/js/irRuntime/core.kt +++ b/libraries/stdlib/js/irRuntime/core.kt @@ -108,4 +108,7 @@ internal fun newThrowable(message: String?, cause: Throwable?): Throwable { throwable.cause = cause throwable.name = "Throwable" return throwable -} \ No newline at end of file +} + +internal fun boxIntrinsic(x: T): R = error("Should be lowered") +internal fun unboxIntrinsic(x: T): R = error("Should be lowered")