Psi2ir: keep nullability when substituting function type for SAM type

After we added "careful approximation of contravariant projections" in
584b70719e, some SAM conversions started to require an additional
implicit cast of the functional value before it is converted to the SAM
interface. The target type of this implicit cast was computed
incorrectly because it didn't contain nullability of the SAM type. This
could lead to a situation where a nullable value was incorrectly cast to
a non-null type, which caused a missing null check and NPE at runtime.

For example, let's consider the test `kt54600.kt`. SAM conversion
happens in the constructor call `J(filter)`. Before 584b70719e, the IR
for that argument was (irrelevant things are omitted for simplicity):

  TYPE_OP SAM_CONVERSION type=Condition<String!>!
    GET_VAR filter type=Function1<String, Boolean>?

After 584b70719e, the IR became:

  TYPE_OP SAM_CONVERSION type=Condition<Any?>!
    TYPE_OP IMPLICIT_CAST type=Function1<Any?, Boolean>
      GET_VAR filter type=Function1<String, Boolean>?

Note the two changes:
* The resulting SAM type changed from `Condition<String!>` to
  `Condition<Any?>`. This is exactly the point of the "careful
  approximation" change, because just erasing the "in" projection from
  the parameter type is incorrect, see the explanation for that change.
* The value is now implicitly cast to the _non-null_ function type
  before it is SAM-converted. The presence of the cast is fine, but the
  fact that it's to a non-null type is an oversight.

The target type for this cast is computed at
`KotlinType.getSubstitutedFunctionTypeForSamType` in psi2ir. Now it
extracts the nullability from the SAM type and retains it in the
resulting function type.

After this change, the IR for the argument becomes:

  TYPE_OP SAM_CONVERSION type=Condition<Any?>!
    TYPE_OP IMPLICIT_CAST type=Function1<Any?, Boolean>!
      GET_VAR filter type=Function1<String, Boolean>?

Note that the target type is now flexible, as the resulting SAM type.
Another option would be to make it nullable, as the type of the
functional value, but there doesn't seem to be any difference.

 #KT-54600 Fixed
This commit is contained in:
Alexander Udalov
2022-10-24 23:39:52 +02:00
committed by Space Team
parent c0789b5207
commit b42a7be0de
10 changed files with 101 additions and 11 deletions
@@ -46523,6 +46523,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/sam/kt52417.kt");
}
@Test
@TestMetadata("kt54600.kt")
public void testKt54600() throws Exception {
runTest("compiler/testData/codegen/box/sam/kt54600.kt");
}
@Test
@TestMetadata("nonInlinedSamWrapper.kt")
public void testNonInlinedSamWrapper() throws Exception {
@@ -13,9 +13,7 @@ import org.jetbrains.kotlin.descriptors.synthetic.FunctionInterfaceAdapterExtens
import org.jetbrains.kotlin.descriptors.synthetic.FunctionInterfaceConstructorDescriptor
import org.jetbrains.kotlin.resolve.sam.getFunctionTypeForAbstractMethod
import org.jetbrains.kotlin.resolve.sam.getSingleAbstractMethodOrNull
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.*
fun GeneratorExtensions.SamConversion.isSamType(kotlinType: KotlinType): Boolean {
val descriptor = kotlinType.constructor.declarationDescriptor
@@ -35,12 +33,23 @@ fun CallableDescriptor.getOriginalForFunctionInterfaceAdapter() =
null
}
fun KotlinType.getSubstitutedFunctionTypeForSamType(): KotlinType {
fun KotlinType.getSubstitutedFunctionTypeForSamType(): KotlinType =
when (val unwrapped = this.unwrap()) {
is SimpleType -> unwrapped.getSubstitutedFunctionTypeForSamType()
is FlexibleType -> KotlinTypeFactory.flexibleType(
unwrapped.lowerBound.getSubstitutedFunctionTypeForSamType(),
unwrapped.upperBound.getSubstitutedFunctionTypeForSamType(),
)
}
private fun SimpleType.getSubstitutedFunctionTypeForSamType(): SimpleType {
val descriptor = constructor.declarationDescriptor as? ClassDescriptor
?: throw AssertionError("SAM should be represented by a class: $this")
val singleAbstractMethod = getSingleAbstractMethodOrNull(descriptor)
?: throw AssertionError("$descriptor should have a single abstract method")
val unsubstitutedFunctionType = getFunctionTypeForAbstractMethod(singleAbstractMethod, false)
return TypeSubstitutor.create(this).substitute(unsubstitutedFunctionType, Variance.INVARIANT)
val unsubstitutedFunctionType = getFunctionTypeForAbstractMethod(singleAbstractMethod, false).makeNullableAsSpecified(isMarkedNullable)
val result = TypeSubstitutor.create(this).substitute(unsubstitutedFunctionType, Variance.INVARIANT)
?: throw AssertionError("Failed to substitute function type $unsubstitutedFunctionType corresponding to $this")
return result as? SimpleType
?: throw AssertionError("SAM type substitution result is not a simple type: $this -> $result")
}
+15
View File
@@ -0,0 +1,15 @@
FILE fqName:<root> fileName:/box.kt
FUN name:box visibility:public modality:FINAL <> () returnType:kotlin.String
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun box (): kotlin.String declared in <root>'
CALL 'public final fun foo (filter: kotlin.Function1<kotlin.String, kotlin.Boolean>?): kotlin.String declared in <root>' type=kotlin.String origin=null
filter: CONST Null type=kotlin.Nothing? value=null
FUN name:foo visibility:public modality:FINAL <> (filter:kotlin.Function1<kotlin.String, kotlin.Boolean>?) returnType:kotlin.String
VALUE_PARAMETER name:filter index:0 type:kotlin.Function1<kotlin.String, kotlin.Boolean>?
BLOCK_BODY
TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit
CONSTRUCTOR_CALL 'public constructor <init> (filter: @[FlexibleNullability] <root>.Condition<in @[FlexibleNullability] kotlin.String?>?) declared in <root>.J' type=<root>.J origin=null
filter: TYPE_OP type=@[FlexibleNullability] <root>.Condition<@[FlexibleNullability] kotlin.String?>? origin=SAM_CONVERSION typeOperand=@[FlexibleNullability] <root>.Condition<@[FlexibleNullability] kotlin.String?>?
GET_VAR 'filter: kotlin.Function1<kotlin.String, kotlin.Boolean>? declared in <root>.foo' type=kotlin.Function1<kotlin.String, kotlin.Boolean>? origin=null
RETURN type=kotlin.Nothing from='public final fun foo (filter: kotlin.Function1<kotlin.String, kotlin.Boolean>?): kotlin.String declared in <root>'
CONST String type=kotlin.String value="OK"
+16
View File
@@ -0,0 +1,16 @@
FILE fqName:<root> fileName:/box.kt
FUN name:box visibility:public modality:FINAL <> () returnType:kotlin.String
BLOCK_BODY
RETURN type=kotlin.Nothing from='public final fun box (): kotlin.String declared in <root>'
CALL 'public final fun foo (filter: kotlin.Function1<kotlin.String, kotlin.Boolean>?): kotlin.String declared in <root>' type=kotlin.String origin=null
filter: CONST Null type=kotlin.Nothing? value=null
FUN name:foo visibility:public modality:FINAL <> (filter:kotlin.Function1<kotlin.String, kotlin.Boolean>?) returnType:kotlin.String
VALUE_PARAMETER name:filter index:0 type:kotlin.Function1<kotlin.String, kotlin.Boolean>?
BLOCK_BODY
TYPE_OP type=kotlin.Unit origin=IMPLICIT_COERCION_TO_UNIT typeOperand=kotlin.Unit
CONSTRUCTOR_CALL 'public constructor <init> (filter: @[FlexibleNullability] <root>.Condition<in @[FlexibleNullability] kotlin.String?>?) declared in <root>.J' type=<root>.J origin=null
filter: TYPE_OP type=@[FlexibleNullability] <root>.Condition<kotlin.Any?>? origin=SAM_CONVERSION typeOperand=@[FlexibleNullability] <root>.Condition<kotlin.Any?>?
TYPE_OP type=@[FlexibleNullability] kotlin.Function1<@[ParameterName(name = 't')] kotlin.Any?, kotlin.Boolean>? origin=IMPLICIT_CAST typeOperand=@[FlexibleNullability] kotlin.Function1<@[ParameterName(name = 't')] kotlin.Any?, kotlin.Boolean>?
GET_VAR 'filter: kotlin.Function1<kotlin.String, kotlin.Boolean>? declared in <root>.foo' type=kotlin.Function1<kotlin.String, kotlin.Boolean>? origin=null
RETURN type=kotlin.Nothing from='public final fun foo (filter: kotlin.Function1<kotlin.String, kotlin.Boolean>?): kotlin.String declared in <root>'
CONST String type=kotlin.String value="OK"
+27
View File
@@ -0,0 +1,27 @@
// TARGET_BACKEND: JVM
// DUMP_IR
// FILE: box.kt
fun box(): String =
foo(null)
fun foo(filter: ((String) -> Boolean)?): String {
J(filter)
return "OK"
}
// FILE: J.java
public class J {
public J(Condition<? super String> filter) {
if (filter != null) {
filter.value("");
}
}
}
// FILE: Condition.java
public interface Condition<T> {
boolean value(T t);
}
@@ -106,10 +106,9 @@ FILE fqName:<root> fileName:/samConversionsWithSmartCasts.kt
CALL 'public open fun run1 (r: @[FlexibleNullability] java.lang.Runnable?): kotlin.Unit declared in <root>.J' type=kotlin.Unit origin=null
$this: CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.J' type=<root>.J origin=null
r: TYPE_OP type=@[FlexibleNullability] java.lang.Runnable? origin=SAM_CONVERSION typeOperand=@[FlexibleNullability] java.lang.Runnable?
TYPE_OP type=kotlin.Function0<kotlin.Unit> origin=IMPLICIT_NOTNULL typeOperand=kotlin.Function0<kotlin.Unit>
CALL 'public open fun id <T> (x: @[FlexibleNullability] T of <root>.J.id?): @[FlexibleNullability] T of <root>.J.id? declared in <root>.J' type=@[FlexibleNullability] kotlin.Function0<kotlin.Unit>? origin=null
<T>: @[FlexibleNullability] kotlin.Function0<kotlin.Unit>?
x: GET_VAR 'a: kotlin.Function0<kotlin.Unit> declared in <root>.test8' type=kotlin.Function0<kotlin.Unit> origin=null
CALL 'public open fun id <T> (x: @[FlexibleNullability] T of <root>.J.id?): @[FlexibleNullability] T of <root>.J.id? declared in <root>.J' type=@[FlexibleNullability] kotlin.Function0<kotlin.Unit>? origin=null
<T>: @[FlexibleNullability] kotlin.Function0<kotlin.Unit>?
x: GET_VAR 'a: kotlin.Function0<kotlin.Unit> declared in <root>.test8' type=kotlin.Function0<kotlin.Unit> origin=null
FUN name:test9 visibility:public modality:FINAL <> () returnType:kotlin.Unit
BLOCK_BODY
CALL 'public open fun run1 (r: @[FlexibleNullability] java.lang.Runnable?): kotlin.Unit declared in <root>.J' type=kotlin.Unit origin=null
@@ -58,9 +58,10 @@ fun test7(a: Function1<Int, Int>) {
}
fun test8(a: Function0<Unit>) {
J().run1(r = id<@FlexibleNullability Function0<Unit>?>(x = a) /*!! Function0<Unit> */ /*-> @FlexibleNullability Runnable? */)
J().run1(r = id<@FlexibleNullability Function0<Unit>?>(x = a) /*-> @FlexibleNullability Runnable? */)
}
fun test9() {
J().run1(r = ::test9 /*-> @FlexibleNullability Runnable? */)
}
@@ -44909,6 +44909,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/sam/kt52417.kt");
}
@Test
@TestMetadata("kt54600.kt")
public void testKt54600() throws Exception {
runTest("compiler/testData/codegen/box/sam/kt54600.kt");
}
@Test
@TestMetadata("nonInlinedSamWrapper.kt")
public void testNonInlinedSamWrapper() throws Exception {
@@ -46523,6 +46523,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/sam/kt52417.kt");
}
@Test
@TestMetadata("kt54600.kt")
public void testKt54600() throws Exception {
runTest("compiler/testData/codegen/box/sam/kt54600.kt");
}
@Test
@TestMetadata("nonInlinedSamWrapper.kt")
public void testNonInlinedSamWrapper() throws Exception {
@@ -36225,6 +36225,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/sam/kt52417.kt");
}
@TestMetadata("kt54600.kt")
public void testKt54600() throws Exception {
runTest("compiler/testData/codegen/box/sam/kt54600.kt");
}
@TestMetadata("nonInlinedSamWrapper.kt")
public void testNonInlinedSamWrapper() throws Exception {
runTest("compiler/testData/codegen/box/sam/nonInlinedSamWrapper.kt");