PSI2IR: SAM conversion in method arguments of out-projected Java classes

It uses the same logic as an old back-end
(see SamType#createByValueParameter and genericSamProjectedOut.kt),
split into two parts:

1. When inserting SAM casts, use SamType#createByValueParamerer to get
the target SAM type.

2. When inserting implicit casts, cast SAM conversions as arguments of
methods of out-projected types to the original type of value parameter
instead of 'Nothing'.
This commit is contained in:
Dmitry Petrov
2019-12-31 16:35:44 +03:00
parent a55bce801e
commit d27593aeda
13 changed files with 145 additions and 19 deletions
@@ -1422,6 +1422,11 @@ public class Fir2IrTextTestGenerated extends AbstractFir2IrTextTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/ir/irText/expressions/sam"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("genericSamProjectedOut.kt")
public void testGenericSamProjectedOut() throws Exception {
runTest("compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.kt");
}
@TestMetadata("samByProjectedType.kt")
public void testSamByProjectedType() throws Exception {
runTest("compiler/testData/ir/irText/expressions/sam/samByProjectedType.kt");
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.backend.jvm
import org.jetbrains.kotlin.backend.common.ir.createParameterDeclarations
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.codegen.SamType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
import org.jetbrains.kotlin.ir.declarations.IrClass
@@ -52,6 +50,11 @@ class JvmGeneratorExtensions(private val generateFacades: Boolean = true) : Gene
override fun isSamType(type: KotlinType): Boolean =
SingleAbstractMethodUtils.isSamType(type)
override fun getSamTypeInfoForValueParameter(valueParameter: ValueParameterDescriptor): KotlinType? {
val samType = SamType.createByValueParameter(valueParameter) ?: return null
return samType.type
}
override fun getSubstitutedFunctionTypeForSamType(samType: KotlinType): KotlinType {
val descriptor = samType.constructor.declarationDescriptor as? JavaClassDescriptor
?: throw AssertionError("SAM should be represented by a Java class: $samType")
@@ -405,8 +405,10 @@ private fun StatementGenerator.pregenerateValueArguments(call: CallBuilder, reso
}
fun StatementGenerator.generateSamConversionForValueArgumentsIfRequired(call: CallBuilder, resolvedCall: ResolvedCall<*>) {
val samConversion = context.extensions.samConversion
val originalDescriptor = resolvedCall.resultingDescriptor
val underlyingDescriptor = context.extensions.samConversion.getOriginalForSamAdapter(originalDescriptor) ?: originalDescriptor
val underlyingDescriptor = samConversion.getOriginalForSamAdapter(originalDescriptor) ?: originalDescriptor
val originalValueParameters = originalDescriptor.valueParameters
val underlyingValueParameters = underlyingDescriptor.valueParameters
@@ -432,34 +434,38 @@ fun StatementGenerator.generateSamConversionForValueArgumentsIfRequired(call: Ca
val typeSubstitutor = TypeSubstitutor.create(substitutionContext)
for (i in underlyingValueParameters.indices) {
val underlyingParameterType = underlyingValueParameters[i].type
val underlyingValueParameter = underlyingValueParameters[i]
val originalUnderlyingParameterType = underlyingValueParameter.original.type
if (partialSamConversionIsSupported && resolvedCall is NewResolvedCallImpl<*>) {
// TODO support SAM conversion of varargs
val argument = resolvedCall.valueArguments[originalValueParameters[i]]?.arguments?.singleOrNull() ?: continue
if (resolvedCall.getExpectedTypeForSamConvertedArgument(argument) == null) continue
resolvedCall.getExpectedTypeForSamConvertedArgument(argument) ?: continue
} else {
if (!context.extensions.samConversion.isSamType(underlyingParameterType)) continue
if (!context.extensions.samConversion.isSamType(originalUnderlyingParameterType)) continue
if (!originalValueParameters[i].type.isFunctionTypeOrSubtype) continue
}
val samKotlinType = samConversion.getSamTypeInfoForValueParameter(underlyingValueParameter) ?: continue
val originalArgument = call.irValueArgumentsByIndex[i] ?: continue
val expectedArgumentType = typeSubstitutor.substitute(underlyingParameterType, Variance.INVARIANT)
val substitutedSamType = typeSubstitutor.substitute(samKotlinType, Variance.INVARIANT)
?: throw AssertionError(
"Failed to substitute value argument type in SAM conversion: " +
"underlyingParameterType=$underlyingParameterType, " +
"underlyingParameterType=$originalUnderlyingParameterType, " +
"substitutionContext=$substitutionContext"
)
val targetType = expectedArgumentType.toIrType()
val irSamType = substitutedSamType.toIrType()
call.irValueArgumentsByIndex[i] =
IrTypeOperatorCallImpl(
originalArgument.startOffset, originalArgument.endOffset,
targetType,
irSamType,
IrTypeOperator.SAM_CONVERSION,
targetType,
castArgumentToFunctionalInterfaceForSamType(originalArgument, expectedArgumentType)
irSamType,
castArgumentToFunctionalInterfaceForSamType(originalArgument, substitutedSamType)
)
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.psi2ir.generators
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.ir.util.StubGeneratorExtensions
import org.jetbrains.kotlin.types.KotlinType
@@ -23,6 +24,9 @@ open class GeneratorExtensions : StubGeneratorExtensions() {
open fun isSamType(type: KotlinType): Boolean = false
open fun getSamTypeInfoForValueParameter(valueParameter: ValueParameterDescriptor): KotlinType? =
throw UnsupportedOperationException("SAM conversion is not supported in this configuration (valueParameter=$valueParameter)")
open fun getSubstitutedFunctionTypeForSamType(samType: KotlinType): KotlinType =
throw UnsupportedOperationException("SAM conversion is not supported in this configuration (samType=$samType)")
@@ -121,11 +121,23 @@ internal class InsertImplicitCasts(
for (index in substitutedDescriptor.valueParameters.indices) {
val argument = getValueArgument(index) ?: continue
val parameterType = substitutedDescriptor.valueParameters[index].type
putValueArgument(index, argument.cast(parameterType))
// Hack to support SAM conversions on out-projected types.
// See SamType#createByValueParameter and genericSamProjectedOut.kt for more details.
val expectedType =
if (argument.isSamConversion() && KotlinBuiltIns.isNothing(parameterType))
substitutedDescriptor.original.valueParameters[index].type.replaceArgumentsWithNothing()
else
parameterType
putValueArgument(index, argument.cast(expectedType))
}
}
}
private fun IrExpression.isSamConversion(): Boolean =
this is IrTypeOperatorCall && operator == IrTypeOperator.SAM_CONVERSION
override fun visitBlockBody(body: IrBlockBody): IrBody =
body.transformPostfix {
statements.forEachIndexed { i, irStatement ->
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: example/Hello.java
@@ -1,5 +1,4 @@
// SKIP_JDK6
// IGNORE_BACKEND: JVM_IR
// FILE: Custom.java
class Custom<K, V> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// WITH_RUNTIME
// FILE: Promise.java
import org.jetbrains.annotations.NotNull;
@@ -0,0 +1,25 @@
FILE fqName:<root> fileName:/genericSamProjectedOut.kt
FUN name:test visibility:public modality:FINAL <> (a:example.SomeJavaClass<out kotlin.String>) returnType:kotlin.Unit
VALUE_PARAMETER name:a index:0 type:example.SomeJavaClass<out kotlin.String>
BLOCK_BODY
CALL 'public open fun someFunction (hello: example.Hello<kotlin.String?>?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
CALL 'public open fun plus (hello: example.Hello<kotlin.String?>?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
CALL 'public open fun get (hello: example.Hello<kotlin.String?>?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
@@ -0,0 +1,38 @@
// WITH_RUNTIME
// FILE: genericSamProjectedOut.kt
import example.SomeJavaClass
fun test(a: SomeJavaClass<out String>) {
// a::someFunction parameter has type of Nothing
// while it's completely safe to pass a lambda for a SAM
// since Hello is effectively contravariant by its parameter
a.someFunction {}
a + {}
a[{}]
}
// FILE: example/Hello.java
package example;
@FunctionalInterface
public interface Hello<A> {
void invoke(A a);
}
// FILE: example/SomeJavaClass.java
package example;
public class SomeJavaClass<A> {
public void someFunction(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
public void plus(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
public void get(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
}
@@ -0,0 +1,31 @@
FILE fqName:<root> fileName:/genericSamProjectedOut.kt
FUN name:test visibility:public modality:FINAL <> (a:example.SomeJavaClass<out kotlin.String>) returnType:kotlin.Unit
VALUE_PARAMETER name:a index:0 type:example.SomeJavaClass<out kotlin.String>
BLOCK_BODY
CALL 'public open fun someFunction (hello: example.Hello<A of example.SomeJavaClass?>?): kotlin.Unit declared in example.SomeJavaClass' type=kotlin.Unit origin=null
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: TYPE_OP type=example.Hello<kotlin.Nothing>? origin=SAM_CONVERSION typeOperand=example.Hello<kotlin.Nothing>?
FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (it: kotlin.String?): kotlin.Unit declared in <root>.test'
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
CALL 'public open fun plus (hello: example.Hello<A of example.SomeJavaClass?>?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=PLUS
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: TYPE_OP type=example.Hello<kotlin.Nothing>? origin=SAM_CONVERSION typeOperand=example.Hello<kotlin.Nothing>?
FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (it: kotlin.String?): kotlin.Unit declared in <root>.test'
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
CALL 'public open fun get (hello: example.Hello<A of example.SomeJavaClass?>?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=GET_ARRAY_ELEMENT
$this: GET_VAR 'a: example.SomeJavaClass<out kotlin.String> declared in <root>.test' type=example.SomeJavaClass<out kotlin.String> origin=null
hello: TYPE_OP type=example.Hello<kotlin.Nothing>? origin=SAM_CONVERSION typeOperand=example.Hello<kotlin.Nothing>?
FUN_EXPR type=kotlin.Function1<kotlin.String?, kotlin.Unit> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (it:kotlin.String?) returnType:kotlin.Unit
VALUE_PARAMETER name:it index:0 type:kotlin.String?
BLOCK_BODY
RETURN type=kotlin.Nothing from='local final fun <anonymous> (it: kotlin.String?): kotlin.Unit declared in <root>.test'
GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit
@@ -2,7 +2,7 @@ FILE fqName:<root> fileName:/samByProjectedType.kt
FUN name:test1 visibility:public modality:FINAL <> () returnType:kotlin.Unit
BLOCK_BODY
CALL 'public open fun bar (j: <root>.J<*>?): kotlin.Unit declared in <root>.H' type=kotlin.Unit origin=null
j: TYPE_OP type=<root>.J<*>? origin=SAM_CONVERSION typeOperand=<root>.J<*>?
j: TYPE_OP type=<root>.J<kotlin.Any?>? origin=SAM_CONVERSION typeOperand=<root>.J<kotlin.Any?>?
FUN_EXPR type=kotlin.Function1<kotlin.Any, kotlin.Any> origin=LAMBDA
FUN LOCAL_FUNCTION_FOR_LAMBDA name:<anonymous> visibility:local modality:FINAL <> (x:kotlin.Any) returnType:kotlin.Any
VALUE_PARAMETER name:x index:0 type:kotlin.Any
@@ -1421,6 +1421,11 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/ir/irText/expressions/sam"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("genericSamProjectedOut.kt")
public void testGenericSamProjectedOut() throws Exception {
runTest("compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.kt");
}
@TestMetadata("samByProjectedType.kt")
public void testSamByProjectedType() throws Exception {
runTest("compiler/testData/ir/irText/expressions/sam/samByProjectedType.kt");