JS IR: make CodeCleaner more conservative
The CodeCleaner treated all expressions with type `Nothing` as non-terminating. This is not true for cases when return type is generic, e.g: ``` fun <T> foo(): T = Any() as T foo<Nothing>() // type: Nothing ``` This change makes the CodeCleaner more conservative, so that it doesn't treat to such functions as non-terminating. This eliminates the need to change call types from Nothing to Unit in KotlinNothingValueExceptionLowering.
This commit is contained in:
+1
-23
@@ -38,36 +38,14 @@ class KotlinNothingValueExceptionLowering(
|
|||||||
// call ThrowKotlinNothingValueException(): Nothing
|
// call ThrowKotlinNothingValueException(): Nothing
|
||||||
// }: 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 {
|
backendContext.createIrBuilder(parent, expression.startOffset, expression.endOffset).run {
|
||||||
irBlock(expression, null, context.irBuiltIns.nothingType) {
|
irBlock(expression, null, context.irBuiltIns.nothingType) {
|
||||||
+super.visitCall(changeTypeToUnit(expression))
|
+super.visitCall(expression)
|
||||||
+irCall(backendContext.ir.symbols.ThrowKotlinNothingValueException)
|
+irCall(backendContext.ir.symbols.ThrowKotlinNothingValueException)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
super.visitCall(expression)
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+25
-1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower.cleanup
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
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.backend.js.utils.isPure
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
@@ -62,7 +63,7 @@ private class CodeCleaner : IrElementVisitorVoid {
|
|||||||
unreachable -> false
|
unreachable -> false
|
||||||
it is IrExpression && it.isPure(true) -> false
|
it is IrExpression && it.isPure(true) -> false
|
||||||
else -> {
|
else -> {
|
||||||
unreachable = it is IrExpression && it.type.isNothing()
|
unreachable = it.doesNotReturn()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,29 @@ private class CodeCleaner : IrElementVisitorVoid {
|
|||||||
statements += newStatements
|
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 <T> foo(): T = Any() as T`, which could be used as `foo<Nothing>()` 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) {
|
override fun visitElement(element: IrElement) {
|
||||||
element.acceptChildrenVoid(this)
|
element.acceptChildrenVoid(this)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user