PSI2IR: Post-process return expressions based on expected return type

Consider the following example:

Java:
  public class J {
    public static String foo() { return null; }
  }

Kotlin:
  fun check(fn: () -> Any) = fn()
  fun test() = check { J.foo() }

When a lambda expression returns a value of platform type ('String!'),
corresponding lambda has platform type in its return type, which is
approximated to corresponding nullable type ('String?') in IR.
However, the lambda itself could occur in position with a functional
expected type ('() -> Any'). This implies an extra implicit cast on a
return value of lambda expression ('J.foo()'), although it conforms to
the return type of lambda.
This commit is contained in:
Dmitry Petrov
2019-12-30 19:38:52 +03:00
parent 0e4e5ac287
commit 8054e2960e
7 changed files with 241 additions and 59 deletions
@@ -17,9 +17,12 @@
package org.jetbrains.kotlin.psi2ir.transformations
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
@@ -35,6 +38,7 @@ import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.util.TypeTranslator
import org.jetbrains.kotlin.ir.util.coerceToUnitIfNeeded
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.psi2ir.containsNull
import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions
@@ -43,19 +47,16 @@ import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.*
fun insertImplicitCasts(element: IrElement, context: GeneratorContext) {
element.transformChildren(
InsertImplicitCasts(
context.builtIns,
context.irBuiltIns,
context.typeTranslator,
context.callToSubstitutedDescriptorMap,
context.extensions
),
null
)
InsertImplicitCasts(
context.builtIns,
context.irBuiltIns,
context.typeTranslator,
context.callToSubstitutedDescriptorMap,
context.extensions
).run(element)
}
open class InsertImplicitCasts(
internal class InsertImplicitCasts(
private val builtIns: KotlinBuiltIns,
private val irBuiltIns: IrBuiltIns,
private val typeTranslator: TypeTranslator,
@@ -63,6 +64,22 @@ open class InsertImplicitCasts(
private val generatorExtensions: GeneratorExtensions
) : IrElementTransformerVoid() {
private val expectedFunctionExpressionReturnType = hashMapOf<FunctionDescriptor, IrType>()
private val returnExpressionsToBePostprocessed = arrayListOf<IrReturn>()
fun run(element: IrElement) {
element.transformChildrenVoid(this)
for (irReturn in returnExpressionsToBePostprocessed) {
postprocessReturnExpression(irReturn)
}
}
private fun postprocessReturnExpression(irReturn: IrReturn) {
val returnTarget = irReturn.returnTarget
val expectedReturnType = expectedFunctionExpressionReturnType[returnTarget] ?: return
irReturn.value = irReturn.value.cast(expectedReturnType)
}
private fun KotlinType.toIrType() = typeTranslator.translateType(this)
private val IrMemberAccessExpression.substitutedDescriptor
@@ -139,6 +156,7 @@ open class InsertImplicitCasts(
value = if (expression.returnTargetSymbol is IrConstructorSymbol) {
value.coerceToUnit()
} else {
returnExpressionsToBePostprocessed.add(expression)
value.cast(expression.returnTarget.returnType)
}
}
@@ -256,10 +274,23 @@ open class InsertImplicitCasts(
private fun IrExpression.cast(irType: IrType): IrExpression =
cast(irType.originalKotlinType)
private fun KotlinType.getFunctionReturnTypeOrNull(): KotlinType? =
if (isFunctionType || isSuspendFunctionType)
arguments.last().type
else
null
private fun IrExpression.cast(expectedType: KotlinType?): IrExpression {
if (expectedType == null) return this
if (expectedType.isError) return this
if (this is IrFunctionExpression) {
val expectedFunctionReturnType = expectedType.getFunctionReturnTypeOrNull()
if (expectedFunctionReturnType != null) {
expectedFunctionExpressionReturnType[function.descriptor] = expectedFunctionReturnType.toIrType()
}
}
// TODO here we can have non-denotable KotlinTypes (both in 'this@cast.type' and 'expectedType').
val notNullableExpectedType = expectedType.makeNotNullable()
@@ -317,12 +348,12 @@ open class InsertImplicitCasts(
)
}
protected open fun IrExpression.coerceToUnit(): IrExpression {
private fun IrExpression.coerceToUnit(): IrExpression {
val valueType = getKotlinType(this)
return coerceToUnitIfNeeded(valueType, irBuiltIns)
}
protected fun getKotlinType(irExpression: IrExpression) =
private fun getKotlinType(irExpression: IrExpression) =
irExpression.type.originalKotlinType!!
private fun KotlinType.isBuiltInIntegerType(): Boolean =
@@ -335,4 +366,3 @@ open class InsertImplicitCasts(
KotlinBuiltIns.isUInt(this) ||
KotlinBuiltIns.isULong(this)
}