diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/KotlinNothingValueExceptionLowering.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/KotlinNothingValueExceptionLowering.kt index a047b4982c5..da3277c675e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/KotlinNothingValueExceptionLowering.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/KotlinNothingValueExceptionLowering.kt @@ -38,36 +38,14 @@ class KotlinNothingValueExceptionLowering( // call ThrowKotlinNothingValueException(): Nothing // }: Nothing // - // Changing type of 'foo' to 'kotlin.Unit' is requires so that the 'ThrowKotlinNothingValueException(): Nothing' - // is not considered dead code and is not removed. - // Note that type 'kotlin.Nothing' might be inferred in some cases of projected types. - // See KT-30330 for an example of such code where call of type 'kotlin.Nothing' terminates and produces some value - // (although doing so by subverting the type system). backendContext.createIrBuilder(parent, expression.startOffset, expression.endOffset).run { irBlock(expression, null, context.irBuiltIns.nothingType) { - +super.visitCall(changeTypeToUnit(expression)) + +super.visitCall(expression) +irCall(backendContext.ir.symbols.ThrowKotlinNothingValueException) } } } else { super.visitCall(expression) } - - private fun changeTypeToUnit(call: IrCall): IrCall = - IrCallImpl( - call.startOffset, call.endOffset, - backendContext.irBuiltIns.unitType, - call.symbol, - call.typeArgumentsCount, call.valueArgumentsCount, call.origin, call.superQualifierSymbol - ).also { newCall -> - for (i in 0 until call.typeArgumentsCount) { - newCall.putTypeArgument(i, call.getTypeArgument(i)) - } - newCall.dispatchReceiver = call.dispatchReceiver - newCall.extensionReceiver = call.extensionReceiver - for (i in 0 until call.valueArgumentsCount) { - newCall.putValueArgument(i, call.getValueArgument(i)) - } - } } } \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/cleanup/CleanupLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/cleanup/CleanupLowering.kt index 385d3ae7f4e..caae71e2b23 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/cleanup/CleanupLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/cleanup/CleanupLowering.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower.cleanup import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.backend.js.utils.isPure import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.expressions.* @@ -62,7 +63,7 @@ private class CodeCleaner : IrElementVisitorVoid { unreachable -> false it is IrExpression && it.isPure(true) -> false else -> { - unreachable = it is IrExpression && it.type.isNothing() + unreachable = it.doesNotReturn() true } } @@ -73,6 +74,29 @@ private class CodeCleaner : IrElementVisitorVoid { statements += newStatements } + // Checks if it is safe to assume the statement doesn't return (e.g. throws an exception or loops infinitely) + // Takes into account cases like `fun foo(): T = Any() as T`, which could be used as `foo()` and terminate despite the call type `Nothing`. + // Assumes that only functions with explicit return type `Nothing` do not return. + // Also see KotlinNothingValueExceptionLowering.kt + private fun IrStatement.doesNotReturn(): Boolean { + if (this !is IrExpression || !type.isNothing()) return false + + var hasFakeNothingCalls = false + + acceptVoid(object : IrElementVisitorVoid { + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitCall(expression: IrCall) { + super.visitCall(expression) + hasFakeNothingCalls = hasFakeNothingCalls || expression.type.isNothing() && !expression.symbol.owner.returnType.isNothing() + } + }) + + return !hasFakeNothingCalls + } + override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) }