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 6a1e0889294..14f38cbfec4 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 @@ -86,6 +86,7 @@ fun JsIrBackendContext.lower(file: IrFile) { InnerClassConstructorCallsLowering(this).runOnFilePostfix(file) PropertiesLowering().lower(file) InitializersLowering(this, JsLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, false).runOnFilePostfix(file) + MultipleCatchesLowering(this).lower(file) TypeOperatorLowering(this).lower(file) BlockDecomposerLowering(this).runOnFilePostfix(file) SecondaryCtorLowering(this).runOnFilePostfix(file) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt index 69690087b06..caa1f0f2d00 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ir/IrBuilder.kt @@ -12,9 +12,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclarationOriginImpl import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.IrLoop -import org.jetbrains.kotlin.ir.expressions.IrStatementOriginImpl +import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.types.KotlinType @@ -38,6 +36,8 @@ object JsIrBuilder { fun buildReturn(targetSymbol: IrFunctionSymbol, value: IrExpression) = IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetSymbol, value) + fun buildThrow(type: KotlinType, value: IrExpression) = IrThrowImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, value) + fun buildValueParameter(symbol: IrValueParameterSymbol) = IrValueParameterImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, symbol) @@ -45,6 +45,7 @@ object JsIrBuilder { fun buildGetObjectValue(type: KotlinType, classSymbol: IrClassSymbol) = IrGetObjectValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, classSymbol) + fun buildGetClass(expression: IrExpression, type: KotlinType) = IrGetClassImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, expression) fun buildGetValue(symbol: IrValueSymbol) = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol, SYNTHESIZED_STATEMENT) @@ -76,6 +77,12 @@ object JsIrBuilder { UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, cond, thenBranch, elseBranch, SYNTHESIZED_STATEMENT ) + fun buildWhen(type: KotlinType, branches: List) = + IrWhenImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, SYNTHESIZED_STATEMENT, branches) + + fun buildTypeOperator(type: KotlinType, operator: IrTypeOperator, argument: IrExpression, toType: KotlinType, symbol: IrClassifierSymbol) = + IrTypeOperatorCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, operator, toType, argument, symbol) + fun buildNull(type: KotlinType) = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type) fun buildBoolean(type: KotlinType, v: Boolean) = IrConstImpl.boolean(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v) fun buildInt(type: KotlinType, v: Int) = IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BlockDecomposerLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BlockDecomposerLowering.kt index 8293a384918..7773639d1f5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BlockDecomposerLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BlockDecomposerLowering.kt @@ -34,8 +34,8 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont private val constFalse = JsIrBuilder.buildBoolean(context.builtIns.booleanType, false) private val nothingType = context.builtIns.nullableNothingType - private val unitValue = - JsIrBuilder.buildGetObjectValue(context.builtIns.unitType, context.symbolTable.referenceClass(context.builtIns.unit)) + private val unitType = context.builtIns.unitType + private val unitValue = JsIrBuilder.buildGetObjectValue(unitType, context.symbolTable.referenceClass(context.builtIns.unit)) private val unreachableFunction = JsSymbolBuilder.buildSimpleFunction(context.module, Namer.UNREACHABLE_NAME).initialize(type = nothingType) @@ -237,7 +237,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont val conditionResult = loop.condition.accept(expressionVisitor, data) val bodyResult = loop.body?.accept(this, data) - val unitType = context.builtIns.unitType return conditionResult.runIfChanged { bodyResult?.run { assert(status == VisitStatus.KEPT) } @@ -283,7 +282,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont val bodyResult = loop.body?.accept(this, data) val conditionResult = loop.condition.accept(expressionVisitor, data) - val unitType = context.builtIns.unitType return conditionResult.runIfChanged { bodyResult?.run { assert(status == VisitStatus.KEPT) } @@ -411,13 +409,13 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont is IrBlock -> IrBlockImpl( expression.startOffset, expression.endOffset, - context.builtIns.unitType, + unitType, expression.origin ) is IrComposite -> IrCompositeImpl( expression.startOffset, expression.endOffset, - context.builtIns.unitType, + unitType, expression.origin ) else -> error("Unsupported block type") @@ -663,6 +661,43 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont return DecomposedResult(jump, JsIrBuilder.buildCall(unreachableFunction)) } + override fun visitTry(aTry: IrTry, data: VisitData): VisitResult { + val tryResult = aTry.tryResult.accept(expressionVisitor, data) + val catchResults = aTry.catches.map { Pair(it, it.result.accept(expressionVisitor, data)) } + val finallyResult = aTry.finallyExpression?.accept(statementVisitor, data) + + finallyResult?.run { assert(status == VisitStatus.KEPT) } + + val resultSymbol = makeTempVar(aTry.type) + val resultDeclaration = JsIrBuilder.buildVar(resultSymbol) + + val newTryValue = tryResult.runIfChangedOrDefault(aTry.tryResult) { resultValue } + val trySetResult = JsIrBuilder.buildSetVariable(resultSymbol, newTryValue) as IrStatement + + val tryBlock = IrBlockImpl(aTry.tryResult.startOffset, aTry.tryResult.endOffset, unitType).apply { + statements += tryResult.runIfChangedOrDefault(listOf(trySetResult)) { statements + trySetResult } + } + + val catchBlocks = catchResults.map { (original, result) -> + val newCatchResult = result.runIfChangedOrDefault(original.result) { resultValue } + val catchSetResult = JsIrBuilder.buildSetVariable(resultSymbol, newCatchResult) as IrStatement + val catchBlock = IrBlockImpl(original.result.startOffset, original.result.endOffset, unitType) + catchBlock.statements += result.runIfChangedOrDefault(listOf(catchSetResult)) { statements + catchSetResult } + IrCatchImpl(original.startOffset, original.endOffset, original.catchParameter, catchBlock) + } + + val newTry = aTry.run { IrTryImpl(startOffset, endOffset, unitType, tryBlock, catchBlocks, finallyExpression) } + + return DecomposedResult(mutableListOf(resultDeclaration, newTry), JsIrBuilder.buildGetValue(resultSymbol)) + } + + override fun visitSetVariable(expression: IrSetVariable, data: VisitData): VisitResult { + val result = expression.accept(statementVisitor, data) + return if (result.status == VisitStatus.KEPT) { + DecomposedResult(expression, unitValue) + } else result + } + override fun visitLoop(loop: IrLoop, data: VisitData): VisitResult { val result = loop.accept(statementVisitor, data) return if (result.status == VisitStatus.KEPT) { @@ -702,8 +737,6 @@ class BlockDecomposerLowering(val context: JsIrBackendContext) : DeclarationCont // keep it as is if (!(needNewConds || needNewBodies)) return expression - val unitType = context.builtIns.unitType - if (needNewConds) { // [val x = ] when { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MultipleCatchesLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MultipleCatchesLowering.kt new file mode 100644 index 00000000000..3fde18cf9fb --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/MultipleCatchesLowering.kt @@ -0,0 +1,121 @@ +/* + * 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.descriptors.VariableDescriptor +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.backend.js.symbols.JsSymbolBuilder +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrBranchImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrCatchImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrElseBranchImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrTryImpl +import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol +import org.jetbrains.kotlin.ir.visitors.IrElementTransformer +import org.jetbrains.kotlin.types.CommonSupertypes +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.isDynamic +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf + +/** + * Since JS does not support multiple catch blocks by default we should replace them with similar `when` statement, so + * + * try {} + * catch (ex1: Ex1) { catch1(ex1) } + * catch (ex2: Ex2) { catch2(ex2) } + * catch (ex3: Ex3) { catch3(ex3) } + * [catch (exd: dynamic) { catch_dynamic(exd) } ] + * finally {} + * + * is converted into + * + * try {} + * catch (ex: Ex = LCA(Ex1, Ex2, Ex3) { + * when (ex) { + * ex is Ex1 -> catch1((Ex1)ex) + * ex is Ex2 -> catch2((Ex2)ex) + * ex is Ex3 -> catch3((Ex3)ex) + * else throw ex [ | catch_dynamic(ex) ] + * } + * } + * finally {} + */ + +class MultipleCatchesLowering(val context: JsIrBackendContext) : FileLoweringPass { + val litTrue = JsIrBuilder.buildBoolean(context.irBuiltIns.bool, true) + val unitType = context.irBuiltIns.unit + val nothingType = context.irBuiltIns.nothing + + override fun lower(irFile: IrFile) { + irFile.transformChildren(object : IrElementTransformer { + + override fun visitDeclaration(declaration: IrDeclaration, data: IrDeclaration?) = + super.visitDeclaration(declaration, declaration) + + override fun visitTry(aTry: IrTry, data: IrDeclaration?): IrExpression { + aTry.transformChildren(this, data) + + if (aTry.catches.isEmpty()) return aTry.apply { assert(finallyExpression != null) } + + val commonType = mergeTypes(aTry.catches.map { it.catchParameter.type }) + + val pendingExceptionSymbol = JsSymbolBuilder.buildVar(data!!.descriptor, commonType, "\$pending\$", false) + val pendingExceptionDeclaration = JsIrBuilder.buildVar(pendingExceptionSymbol) + val pendingException = JsIrBuilder.buildGetValue(pendingExceptionSymbol) + + val branches = mutableListOf() + + for (catch in aTry.catches) { + assert(!catch.catchParameter.isVar) { "caught exception parameter has to immutable" } + val type = catch.catchParameter.type + val typeSymbol = context.symbolTable.referenceClassifier(type.constructor.declarationDescriptor!!) + val castedPendingException = buildImplicitCast(pendingException, type, typeSymbol) + val catchBody = catch.result.transform(object : IrElementTransformer { + override fun visitGetValue(expression: IrGetValue, data: VariableDescriptor) = + if (expression.descriptor == data) castedPendingException else expression + }, catch.parameter) + + if (type.isDynamic()) { + branches += IrElseBranchImpl(catch.startOffset, catch.endOffset, litTrue, catchBody) + break + } else { + val typeCheck = buildIsCheck(pendingException, type, typeSymbol) + branches += IrBranchImpl(catch.startOffset, catch.endOffset, typeCheck, catchBody) + } + } + + + if (!commonType.isDynamic()) { + val throwStatement = JsIrBuilder.buildThrow(nothingType, pendingException) + branches += IrElseBranchImpl(litTrue, JsIrBuilder.buildBlock(nothingType, listOf(throwStatement))) + } + + val whenStatement = JsIrBuilder.buildWhen(aTry.type, branches) + + val newCatch = aTry.run { + IrCatchImpl(catches.first().startOffset, catches.last().endOffset, pendingExceptionDeclaration, whenStatement) + } + + return aTry.run { IrTryImpl(startOffset, endOffset, type, tryResult, listOf(newCatch), finallyExpression) } + } + + private fun buildIsCheck(value: IrExpression, toType: KotlinType, toTypeSymbol: IrClassifierSymbol) = + JsIrBuilder.buildTypeOperator(context.irBuiltIns.bool, IrTypeOperator.INSTANCEOF, value, toType, toTypeSymbol) + + private fun buildImplicitCast(value: IrExpression, toType: KotlinType, toTypeSymbol: IrClassifierSymbol) = + JsIrBuilder.buildTypeOperator(toType, IrTypeOperator.IMPLICIT_CAST, value, toType, toTypeSymbol) + + private fun mergeTypes(types: List) = CommonSupertypes.commonSupertype(types).also { + assert(it.isSubtypeOf(context.builtIns.throwable.defaultType) || it.isDynamic()) + } + + }, null) + } +} \ 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 fa1e2015f05..5451e9f6476 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 @@ -225,7 +225,9 @@ class SecondaryCtorLowering(val context: JsIrBackendContext) : IrElementTransfor } val fromPrimary = ownerFunc!! is IrConstructor - val newCall = redirectCall(expression, context.secondaryConstructorsMap[target]!!.delegate) + // TODO: what is `deserialized` constructor? + val ctor = context.secondaryConstructorsMap[target] ?: return expression + val newCall = redirectCall(expression, ctor.delegate) val readThis = if (fromPrimary) { IrGetValueImpl( diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TypeOperatorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TypeOperatorLowering.kt index c128f178f75..a47af1dea50 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TypeOperatorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/TypeOperatorLowering.kt @@ -122,7 +122,7 @@ class TypeOperatorLowering(val context: JsIrBackendContext) : FileLoweringPass { private fun lowerImplicitCast(expression: IrTypeOperatorCall) = expression.run { assert(operator == IrTypeOperator.IMPLICIT_CAST) - IrCompositeImpl(startOffset, endOffset, typeOperand, null, listOf(argument)) + argument } // Note: native `instanceOf` is not used which is important because of null-behaviour diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsStatementTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsStatementTransformer.kt index cea675e557f..9bcf3b868c6 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsStatementTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsStatementTransformer.kt @@ -65,6 +65,21 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer SymbolTable.withScope(owner: DeclarationDescriptor, block: () -> T): T { +inline fun SymbolTable.withScope(owner: DeclarationDescriptor, block: SymbolTable.() -> T): T { enterScope(owner) - val result = block() + val result = block(this) leaveScope(owner) return result } \ No newline at end of file