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
@@ -1087,6 +1087,11 @@ public class Fir2IrTextTestGenerated extends AbstractFir2IrTextTest {
runTest("compiler/testData/ir/irText/expressions/multipleThisReferences.kt");
}
@TestMetadata("nullCheckOnLambdaReturn.kt")
public void testNullCheckOnLambdaReturn() throws Exception {
runTest("compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt");
}
@TestMetadata("objectAsCallable.kt")
public void testObjectAsCallable() throws Exception {
runTest("compiler/testData/ir/irText/expressions/objectAsCallable.kt");
@@ -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)
}
@@ -1,49 +1,5 @@
// KOTLIN_CONFIGURATION_FLAGS: +JVM.DISABLE_PARAM_ASSERTIONS
// IGNORE_BACKEND: JVM_IR
// Missing IMPLICIT_NOTNULL casts
// FILE: A.java
import org.jetbrains.annotations.NotNull;
public class A {
@NotNull
public final String NULL = null;
@NotNull
public static final String STATIC_NULL = null;
public String foo() {
return null;
}
public static String staticFoo() {
return null;
}
public A plus(A a) {
return null;
}
public A inc() {
return null;
}
public Object get(Object o) {
return null;
}
public A a() { return this; }
public static class B {
public static B b() { return null; }
}
public static class C {
public static C c() { return null; }
}
}
// FILE: AssertionChecker.kt
// FILE: callAssertions.kt
class AssertionChecker(val illegalStateExpected: Boolean) {
operator fun invoke(name: String, f: () -> Any) {
@@ -124,3 +80,46 @@ fun box(): String {
checkAssertions(true)
return "OK"
}
// FILE: A.java
import org.jetbrains.annotations.NotNull;
public class A {
@NotNull
public final String NULL = null;
@NotNull
public static final String STATIC_NULL = null;
public String foo() {
return null;
}
public static String staticFoo() {
return null;
}
public A plus(A a) {
return null;
}
public A inc() {
return null;
}
public Object get(Object o) {
return null;
}
public A a() { return this; }
public static class B {
public static B b() { return null; }
}
public static class C {
public static C c() { return null; }
}
}
@@ -0,0 +1,60 @@
FILE fqName:<root> fileName:/nullCheckOnLambdaReturn.kt
FUN name:check visibility:public modality:FINAL <> (fn:kotlin.Function0<kotlin.Any>) returnType:kotlin.Any
VALUE_PARAMETER name:fn index:0 type:kotlin.Function0<kotlin.Any>
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun check (fn: kotlin.Function0<kotlin.Any>): kotlin.Any declared in <root>'
CALL 'public abstract fun invoke (): kotlin.Any [operator] declared in kotlin.Function0' type=kotlin.Any origin=null
$this: GET_VAR 'fn: kotlin.Function0<kotlin.Any> declared in <root>.check' type=kotlin.Function0<kotlin.Any> origin=null
FUN name:id visibility:public modality:FINAL <T> (x:T of <root>.id) returnType:T of <root>.id
TYPE_PARAMETER name:T index:0 variance: superTypes:[kotlin.Any?]
VALUE_PARAMETER name:x index:0 type:T of <root>.id
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun id <T> (x: T of <root>.id): T of <root>.id declared in <root>'
GET_VAR 'x: T of <root>.id declared in <root>.id' type=T of <root>.id origin=null
FUN name:test1 visibility:public modality:FINAL <> () returnType:kotlin.Any
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun test1 (): kotlin.Any declared in <root>'
CALL 'public final fun check (fn: kotlin.Function0<kotlin.Any>): kotlin.Any declared in <root>' type=kotlin.Any origin=null
fn: FUN_EXPR type=kotlin.Function0<kotlin.Any> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.Any
BLOCK_BODY
CALL 'public open fun foo (): kotlin.String? [operator] declared in <root>.J' type=kotlin.String? origin=null
PROPERTY name:test2 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
CALL 'public open fun foo (): kotlin.String? [operator] declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test2> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test2 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test2> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
PROPERTY name:test3 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
TYPE_OP type=kotlin.Function0<kotlin.Any> origin=CAST typeOperand=kotlin.Function0<kotlin.Any>
FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
CALL 'public open fun foo (): kotlin.String? [operator] declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test3> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test3 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test3> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
PROPERTY name:test4 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
CALL 'public final fun id <T> (x: T of <root>.id): T of <root>.id declared in <root>' type=kotlin.Function0<kotlin.String?> origin=null
<T>: kotlin.Function0<kotlin.String?>
x: FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
CALL 'public open fun foo (): kotlin.String? [operator] declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test4> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test4 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test4> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
@@ -0,0 +1,17 @@
// FILE: nullCheckOnLambdaReturn.kt
fun check(fn: () -> Any) = fn()
fun <T> id(x: T) = x
fun test1() = check { J.foo() }
val test2: () -> Any = { J.foo() }
val test3: () -> Any = { J.foo() } as () -> Any
val test4: () -> Any = id { J.foo() }
// FILE: J.java
public class J {
public static String foo() { return null; }
}
@@ -0,0 +1,66 @@
FILE fqName:<root> fileName:/nullCheckOnLambdaReturn.kt
FUN name:check visibility:public modality:FINAL <> (fn:kotlin.Function0<kotlin.Any>) returnType:kotlin.Any
VALUE_PARAMETER name:fn index:0 type:kotlin.Function0<kotlin.Any>
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun check (fn: kotlin.Function0<kotlin.Any>): kotlin.Any declared in <root>'
CALL 'public abstract fun invoke (): R of kotlin.Function0 [operator] declared in kotlin.Function0' type=kotlin.Any origin=INVOKE
$this: GET_VAR 'fn: kotlin.Function0<kotlin.Any> declared in <root>.check' type=kotlin.Function0<kotlin.Any> origin=VARIABLE_AS_FUNCTION
FUN name:id visibility:public modality:FINAL <T> (x:T of <root>.id) returnType:T of <root>.id
TYPE_PARAMETER name:T index:0 variance: superTypes:[kotlin.Any?]
VALUE_PARAMETER name:x index:0 type:T of <root>.id
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun id <T> (x: T of <root>.id): T of <root>.id declared in <root>'
GET_VAR 'x: T of <root>.id declared in <root>.id' type=T of <root>.id origin=null
FUN name:test1 visibility:public modality:FINAL <> () returnType:kotlin.Any
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun test1 (): kotlin.Any declared in <root>'
CALL 'public final fun check (fn: kotlin.Function0<kotlin.Any>): kotlin.Any declared in <root>' type=kotlin.Any origin=null
fn: FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String? declared in <root>.test1'
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public open fun foo (): kotlin.String? declared in <root>.J' type=kotlin.String? origin=null
PROPERTY name:test2 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String? declared in <root>.test2'
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public open fun foo (): kotlin.String? declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test2> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test2 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test2> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
PROPERTY name:test3 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
TYPE_OP type=kotlin.Function0<kotlin.Any> origin=CAST typeOperand=kotlin.Function0<kotlin.Any>
FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String? declared in <root>.test3'
CALL 'public open fun foo (): kotlin.String? declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test3> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test3 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test3> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
PROPERTY name:test4 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]
EXPRESSION_BODY
CALL 'public final fun id <T> (x: T of <root>.id): T of <root>.id declared in <root>' type=kotlin.Function0<kotlin.String?> origin=null
<T>: kotlin.Function0<kotlin.String?>
x: FUN_EXPR type=kotlin.Function0<kotlin.String?> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> () returnType:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (): kotlin.String? declared in <root>.test4'
CALL 'public open fun foo (): kotlin.String? declared in <root>.J' type=kotlin.String? origin=null
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test4> visibility:public modality:FINAL <> () returnType:kotlin.Function0<kotlin.Any>
correspondingProperty: PROPERTY name:test4 visibility:public modality:FINAL [val]
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun <get-test4> (): kotlin.Function0<kotlin.Any> declared in <root>'
GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0<kotlin.Any> visibility:private [final,static]' type=kotlin.Function0<kotlin.Any> origin=null
@@ -1086,6 +1086,11 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase {
runTest("compiler/testData/ir/irText/expressions/multipleThisReferences.kt");
}
@TestMetadata("nullCheckOnLambdaReturn.kt")
public void testNullCheckOnLambdaReturn() throws Exception {
runTest("compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt");
}
@TestMetadata("objectAsCallable.kt")
public void testObjectAsCallable() throws Exception {
runTest("compiler/testData/ir/irText/expressions/objectAsCallable.kt");