From 8054e2960e354a598f864672d3d0fc8af70b3ed3 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Mon, 30 Dec 2019 19:38:52 +0300 Subject: [PATCH] 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. --- .../kotlin/fir/Fir2IrTextTestGenerated.java | 5 ++ .../transformations/InsertImplicitCasts.kt | 58 +++++++++--- .../notNullAssertions/callAssertions.kt | 89 +++++++++---------- .../nullCheckOnLambdaReturn.fir.txt | 60 +++++++++++++ .../expressions/nullCheckOnLambdaReturn.kt | 17 ++++ .../expressions/nullCheckOnLambdaReturn.txt | 66 ++++++++++++++ .../kotlin/ir/IrTextTestCaseGenerated.java | 5 ++ 7 files changed, 241 insertions(+), 59 deletions(-) create mode 100644 compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.fir.txt create mode 100644 compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt create mode 100644 compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.txt diff --git a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java index 9d2c945c9c8..edbc7905c1e 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java @@ -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"); diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt index e1fb77b9ec5..f10262992da 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/transformations/InsertImplicitCasts.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() + private val returnExpressionsToBePostprocessed = arrayListOf() + + 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) } - diff --git a/compiler/testData/codegen/boxAgainstJava/notNullAssertions/callAssertions.kt b/compiler/testData/codegen/boxAgainstJava/notNullAssertions/callAssertions.kt index 4f3f68eb21a..99ff8c1da28 100644 --- a/compiler/testData/codegen/boxAgainstJava/notNullAssertions/callAssertions.kt +++ b/compiler/testData/codegen/boxAgainstJava/notNullAssertions/callAssertions.kt @@ -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; } + } +} + diff --git a/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.fir.txt b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.fir.txt new file mode 100644 index 00000000000..47ef579b674 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.fir.txt @@ -0,0 +1,60 @@ +FILE fqName: fileName:/nullCheckOnLambdaReturn.kt + FUN name:check visibility:public modality:FINAL <> (fn:kotlin.Function0) returnType:kotlin.Any + VALUE_PARAMETER name:fn index:0 type:kotlin.Function0 + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun check (fn: kotlin.Function0): kotlin.Any declared in ' + CALL 'public abstract fun invoke (): kotlin.Any [operator] declared in kotlin.Function0' type=kotlin.Any origin=null + $this: GET_VAR 'fn: kotlin.Function0 declared in .check' type=kotlin.Function0 origin=null + FUN name:id visibility:public modality:FINAL (x:T of .id) returnType:T of .id + TYPE_PARAMETER name:T index:0 variance: superTypes:[kotlin.Any?] + VALUE_PARAMETER name:x index:0 type:T of .id + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun id (x: T of .id): T of .id declared in ' + GET_VAR 'x: T of .id declared in .id' type=T of .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 ' + CALL 'public final fun check (fn: kotlin.Function0): kotlin.Any declared in ' type=kotlin.Any origin=null + fn: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.Any + BLOCK_BODY + CALL 'public open fun foo (): kotlin.String? [operator] declared in .J' type=kotlin.String? origin=null + PROPERTY name:test2 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + CALL 'public open fun foo (): kotlin.String? [operator] declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test2 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null + PROPERTY name:test3 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + TYPE_OP type=kotlin.Function0 origin=CAST typeOperand=kotlin.Function0 + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + CALL 'public open fun foo (): kotlin.String? [operator] declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test3 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null + PROPERTY name:test4 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + CALL 'public final fun id (x: T of .id): T of .id declared in ' type=kotlin.Function0 origin=null + : kotlin.Function0 + x: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + CALL 'public open fun foo (): kotlin.String? [operator] declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test4 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null diff --git a/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt new file mode 100644 index 00000000000..fc72c8bde2a --- /dev/null +++ b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.kt @@ -0,0 +1,17 @@ +// FILE: nullCheckOnLambdaReturn.kt +fun check(fn: () -> Any) = fn() + +fun 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; } +} \ No newline at end of file diff --git a/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.txt b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.txt new file mode 100644 index 00000000000..67076913820 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/nullCheckOnLambdaReturn.txt @@ -0,0 +1,66 @@ +FILE fqName: fileName:/nullCheckOnLambdaReturn.kt + FUN name:check visibility:public modality:FINAL <> (fn:kotlin.Function0) returnType:kotlin.Any + VALUE_PARAMETER name:fn index:0 type:kotlin.Function0 + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun check (fn: kotlin.Function0): kotlin.Any declared in ' + 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 declared in .check' type=kotlin.Function0 origin=VARIABLE_AS_FUNCTION + FUN name:id visibility:public modality:FINAL (x:T of .id) returnType:T of .id + TYPE_PARAMETER name:T index:0 variance: superTypes:[kotlin.Any?] + VALUE_PARAMETER name:x index:0 type:T of .id + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun id (x: T of .id): T of .id declared in ' + GET_VAR 'x: T of .id declared in .id' type=T of .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 ' + CALL 'public final fun check (fn: kotlin.Function0): kotlin.Any declared in ' type=kotlin.Any origin=null + fn: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String? declared in .test1' + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public open fun foo (): kotlin.String? declared in .J' type=kotlin.String? origin=null + PROPERTY name:test2 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String? declared in .test2' + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public open fun foo (): kotlin.String? declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test2 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test2 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null + PROPERTY name:test3 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + TYPE_OP type=kotlin.Function0 origin=CAST typeOperand=kotlin.Function0 + FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String? declared in .test3' + CALL 'public open fun foo (): kotlin.String? declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test3 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test3 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null + PROPERTY name:test4 visibility:public modality:FINAL [val] + FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0 visibility:private [final,static] + EXPRESSION_BODY + CALL 'public final fun id (x: T of .id): T of .id declared in ' type=kotlin.Function0 origin=null + : kotlin.Function0 + x: FUN_EXPR type=kotlin.Function0 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> () returnType:kotlin.String? + BLOCK_BODY + RETURN type=kotlin.Nothing from='local final fun (): kotlin.String? declared in .test4' + CALL 'public open fun foo (): kotlin.String? declared in .J' type=kotlin.String? origin=null + FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:kotlin.Function0 + correspondingProperty: PROPERTY name:test4 visibility:public modality:FINAL [val] + BLOCK_BODY + RETURN type=kotlin.Nothing from='public final fun (): kotlin.Function0 declared in ' + GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:test4 type:kotlin.Function0 visibility:private [final,static]' type=kotlin.Function0 origin=null diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index 6167f27e06c..4aed436b311 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -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");