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");