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
|
||||
// }: 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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.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 <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) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user