diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamType.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamType.java index c86e0cd53d1..b02971b88e8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamType.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamType.java @@ -17,13 +17,34 @@ package org.jetbrains.kotlin.codegen; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; +import org.jetbrains.kotlin.codegen.state.TypeMapperUtilsKt; import org.jetbrains.kotlin.descriptors.ClassifierDescriptor; import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor; +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor; import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor; import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils; import org.jetbrains.kotlin.types.KotlinType; +import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt; public class SamType { + @Nullable + public static SamType createByValueParameter(@NotNull ValueParameterDescriptor valueParameter) { + KotlinType originalTypeToUse = + // This can be true in case when the value parameter is in the method of a generic type with out-projection. + // We approximate Inv to Nothing, while Inv itself can be a SAM interface safe to call here + // (see testData genericSamProjectedOut.kt for details) + KotlinBuiltIns.isNothing(valueParameter.getType()) + // In such a case we can't have a proper supertype since wildcards are not allowed there, + // so we use Nothing arguments instead that leads to a raw type used for a SAM wrapper. + // See org.jetbrains.kotlin.codegen.state.KotlinTypeMapper#writeGenericType to understand how + // raw types and Nothing arguments relate. + ? TypeUtilsKt.replaceArgumentsWithNothing(valueParameter.getOriginal().getType()) + : valueParameter.getType(); + + return create(TypeMapperUtilsKt.removeExternalProjections(originalTypeToUse)); + } public static SamType create(@NotNull KotlinType originalType) { if (!SingleAbstractMethodUtils.isSamType(originalType)) return null; return new SamType(originalType); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java index 2515c5ba9da..8b25c728b86 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java @@ -29,7 +29,6 @@ import org.jetbrains.kotlin.cfg.WhenChecker; import org.jetbrains.kotlin.codegen.*; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; import org.jetbrains.kotlin.codegen.state.GenerationState; -import org.jetbrains.kotlin.codegen.state.TypeMapperUtilsKt; import org.jetbrains.kotlin.codegen.when.SwitchCodegenProvider; import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping; import org.jetbrains.kotlin.config.LanguageVersionSettings; @@ -608,7 +607,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { ValueParameterDescriptor adaptedParameter = descriptor.getValueParameters().get(valueParameter.getIndex()); if (KotlinTypeChecker.DEFAULT.equalTypes(adaptedParameter.getType(), valueParameter.getType())) continue; - SamType samType = SamType.create(TypeMapperUtilsKt.removeExternalProjections(valueParameter.getType())); + SamType samType = SamType.createByValueParameter(valueParameter); if (samType == null) continue; ResolvedValueArgument resolvedValueArgument = valueArguments.get(valueParameter.getIndex()); @@ -648,7 +647,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { @Override public void visitConstructorDelegationCall(@NotNull KtConstructorDelegationCall call) { withinUninitializedClass(call, () -> super.visitConstructorDelegationCall(call)); - + checkSamCall(call); } @@ -692,7 +691,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { FunctionDescriptor original = SamCodegenUtil.getOriginalIfSamAdapter((FunctionDescriptor) operationDescriptor); if (original == null) return; - SamType samType = SamType.create(original.getValueParameters().get(0).getType()); + SamType samType = SamType.createByValueParameter(original.getValueParameters().get(0)); if (samType == null) return; IElementType token = expression.getOperationToken(); @@ -718,7 +717,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { List indexExpressions = expression.getIndexExpressions(); List parameters = original.getValueParameters(); for (ValueParameterDescriptor valueParameter : parameters) { - SamType samType = SamType.create(valueParameter.getType()); + SamType samType = SamType.createByValueParameter(valueParameter); if (samType == null) continue; if (isSetter && valueParameter.getIndex() == parameters.size() - 1) { diff --git a/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt b/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt new file mode 100644 index 00000000000..f9e2203b319 --- /dev/null +++ b/compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt @@ -0,0 +1,60 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// 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"); + } +} + +// FILE: main.kt +import example.SomeJavaClass + +fun box(): String { + val a: SomeJavaClass = SomeJavaClass() + + var result = "fail" + + // 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 { + result = it + } + + if (result != "OK") return "fail 1: $result" + result = "fail" + + a + { + result = it + } + + if (result != "OK") return "fail 2: $result" + result = "fail" + + a[{ + result = it + }] + + if (result != "OK") return "fail 3: $result" + + return "OK" +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index c0b09f83667..98fc1ecf941 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -10995,6 +10995,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("genericSamProjectedOut.kt") + public void testGenericSamProjectedOut() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt"); + doTest(fileName); + } + @TestMetadata("lambdaInstanceOf.kt") public void testLambdaInstanceOf() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 14d6692fe7a..3ed5125b0e1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -10995,6 +10995,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("genericSamProjectedOut.kt") + public void testGenericSamProjectedOut() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt"); + doTest(fileName); + } + @TestMetadata("lambdaInstanceOf.kt") public void testLambdaInstanceOf() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index f56c98a6de8..4acb535a818 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -10995,6 +10995,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); } + @TestMetadata("genericSamProjectedOut.kt") + public void testGenericSamProjectedOut() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt"); + doTest(fileName); + } + @TestMetadata("lambdaInstanceOf.kt") public void testLambdaInstanceOf() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 7a858d60bb4..0ab6bc44f9d 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.calls.inference.isCaptured +import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.types.checker.NewCapturedType @@ -170,21 +171,24 @@ fun KotlinType.getImmediateSuperclassNotAny(): KotlinType? { fun KotlinType.asTypeProjection(): TypeProjection = TypeProjectionImpl(this) fun KotlinType.contains(predicate: (UnwrappedType) -> Boolean) = TypeUtils.contains(this, predicate) -fun KotlinType.replaceArgumentsWithStarProjections(): KotlinType { +fun KotlinType.replaceArgumentsWithStarProjections() = replaceArgumentsWith(::StarProjectionImpl) +fun KotlinType.replaceArgumentsWithNothing() = replaceArgumentsWith { it.builtIns.nothingType.asTypeProjection() } + +private inline fun KotlinType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): KotlinType { val unwrapped = unwrap() return when (unwrapped) { is FlexibleType -> KotlinTypeFactory.flexibleType( - unwrapped.lowerBound.replaceArgumentsWithStarProjections(), - unwrapped.upperBound.replaceArgumentsWithStarProjections() + unwrapped.lowerBound.replaceArgumentsWith(replacement), + unwrapped.upperBound.replaceArgumentsWith(replacement) ) - is SimpleType -> unwrapped.replaceArgumentsWithStarProjections() + is SimpleType -> unwrapped.replaceArgumentsWith(replacement) }.inheritEnhancement(unwrapped) } -private fun SimpleType.replaceArgumentsWithStarProjections(): SimpleType { +private inline fun SimpleType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): SimpleType { if (constructor.parameters.isEmpty() || constructor.declarationDescriptor == null) return this - val newArguments = constructor.parameters.map(::StarProjectionImpl) + val newArguments = constructor.parameters.map(replacement) return replace(newArguments) }