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 27a3ed90698..a0b1c22a31f 100644 --- a/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java +++ b/compiler/fir/fir2ir/tests/org/jetbrains/kotlin/fir/Fir2IrTextTestGenerated.java @@ -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"); diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmGeneratorExtensions.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmGeneratorExtensions.kt index 2421df9b928..0e71b8abb74 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmGeneratorExtensions.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmGeneratorExtensions.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") diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt index e6020ae4fa6..aec8ef948a9 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt @@ -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) ) } } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/GeneratorExtensions.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/GeneratorExtensions.kt index 22d4b2c7954..93404c0e2ee 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/GeneratorExtensions.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/GeneratorExtensions.kt @@ -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)") 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 f10262992da..3050f3620e4 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 @@ -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 -> diff --git a/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt b/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt index 5bc57ae98de..41a388b45b4 100644 --- a/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt +++ b/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt @@ -1,5 +1,4 @@ // IGNORE_BACKEND_FIR: JVM_IR -// IGNORE_BACKEND: JVM_IR // TARGET_BACKEND: JVM // WITH_RUNTIME // FILE: example/Hello.java diff --git a/compiler/testData/codegen/boxAgainstJava/sam/kt11519.kt b/compiler/testData/codegen/boxAgainstJava/sam/kt11519.kt index 2b1f58f7e8b..85077fed199 100644 --- a/compiler/testData/codegen/boxAgainstJava/sam/kt11519.kt +++ b/compiler/testData/codegen/boxAgainstJava/sam/kt11519.kt @@ -1,5 +1,4 @@ // SKIP_JDK6 -// IGNORE_BACKEND: JVM_IR // FILE: Custom.java class Custom { diff --git a/compiler/testData/codegen/boxAgainstJava/sam/kt11696.kt b/compiler/testData/codegen/boxAgainstJava/sam/kt11696.kt index 92c0d63c09f..7782f4ac22d 100644 --- a/compiler/testData/codegen/boxAgainstJava/sam/kt11696.kt +++ b/compiler/testData/codegen/boxAgainstJava/sam/kt11696.kt @@ -1,4 +1,3 @@ -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME // FILE: Promise.java import org.jetbrains.annotations.NotNull; diff --git a/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.fir.txt b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.fir.txt new file mode 100644 index 00000000000..f0359da56f0 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.fir.txt @@ -0,0 +1,25 @@ +FILE fqName: fileName:/genericSamProjectedOut.kt + FUN name:test visibility:public modality:FINAL <> (a:example.SomeJavaClass) returnType:kotlin.Unit + VALUE_PARAMETER name:a index:0 type:example.SomeJavaClass + BLOCK_BODY + CALL 'public open fun someFunction (hello: example.Hello?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=null + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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 diff --git a/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.kt b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.kt new file mode 100644 index 00000000000..1daae108237 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.kt @@ -0,0 +1,38 @@ +// WITH_RUNTIME +// FILE: genericSamProjectedOut.kt +import example.SomeJavaClass + +fun test(a: SomeJavaClass) { + // 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 { + void invoke(A a); +} + +// FILE: example/SomeJavaClass.java +package example; + +public class SomeJavaClass { + public void someFunction(Hello hello) { + ((Hello)hello).invoke("OK"); + } + + public void plus(Hello hello) { + ((Hello)hello).invoke("OK"); + } + + public void get(Hello hello) { + ((Hello)hello).invoke("OK"); + } +} diff --git a/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.txt b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.txt new file mode 100644 index 00000000000..d4ca2d12ed2 --- /dev/null +++ b/compiler/testData/ir/irText/expressions/sam/genericSamProjectedOut.txt @@ -0,0 +1,31 @@ +FILE fqName: fileName:/genericSamProjectedOut.kt + FUN name:test visibility:public modality:FINAL <> (a:example.SomeJavaClass) returnType:kotlin.Unit + VALUE_PARAMETER name:a index:0 type:example.SomeJavaClass + BLOCK_BODY + CALL 'public open fun someFunction (hello: example.Hello?): kotlin.Unit declared in example.SomeJavaClass' type=kotlin.Unit origin=null + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: TYPE_OP type=example.Hello? origin=SAM_CONVERSION typeOperand=example.Hello? + FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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 (it: kotlin.String?): kotlin.Unit declared in .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?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=PLUS + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: TYPE_OP type=example.Hello? origin=SAM_CONVERSION typeOperand=example.Hello? + FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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 (it: kotlin.String?): kotlin.Unit declared in .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?): kotlin.Unit [operator] declared in example.SomeJavaClass' type=kotlin.Unit origin=GET_ARRAY_ELEMENT + $this: GET_VAR 'a: example.SomeJavaClass declared in .test' type=example.SomeJavaClass origin=null + hello: TYPE_OP type=example.Hello? origin=SAM_CONVERSION typeOperand=example.Hello? + FUN_EXPR type=kotlin.Function1 origin=LAMBDA + FUN LOCAL_FUNCTION_FOR_LAMBDA name: 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 (it: kotlin.String?): kotlin.Unit declared in .test' + GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Unit modality:FINAL visibility:public superTypes:[kotlin.Any]' type=kotlin.Unit diff --git a/compiler/testData/ir/irText/expressions/sam/samByProjectedType.txt b/compiler/testData/ir/irText/expressions/sam/samByProjectedType.txt index 32042ee7462..2014e76a890 100644 --- a/compiler/testData/ir/irText/expressions/sam/samByProjectedType.txt +++ b/compiler/testData/ir/irText/expressions/sam/samByProjectedType.txt @@ -2,7 +2,7 @@ FILE fqName: fileName:/samByProjectedType.kt FUN name:test1 visibility:public modality:FINAL <> () returnType:kotlin.Unit BLOCK_BODY CALL 'public open fun bar (j: .J<*>?): kotlin.Unit declared in .H' type=kotlin.Unit origin=null - j: TYPE_OP type=.J<*>? origin=SAM_CONVERSION typeOperand=.J<*>? + j: TYPE_OP type=.J? origin=SAM_CONVERSION typeOperand=.J? FUN_EXPR type=kotlin.Function1 origin=LAMBDA FUN LOCAL_FUNCTION_FOR_LAMBDA name: visibility:local modality:FINAL <> (x:kotlin.Any) returnType:kotlin.Any VALUE_PARAMETER name:x index:0 type:kotlin.Any diff --git a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java index 2394f7be2b5..ffeddee9c3d 100644 --- a/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/ir/IrTextTestCaseGenerated.java @@ -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");