Fix runtime CCE in case of out-projected SAM
Probably, when NI is there this fix will become unnecessary because there will be no approximation applied, thus the value parameter will remain Hello<#Captured> instead of Nothing #KT-17171 Fixed
This commit is contained in:
@@ -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<Captured#1> 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);
|
||||
|
||||
+4
-5
@@ -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<KtExpression> indexExpressions = expression.getIndexExpressions();
|
||||
List<ValueParameterDescriptor> 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) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_RUNTIME
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: main.kt
|
||||
import example.SomeJavaClass
|
||||
|
||||
fun box(): String {
|
||||
val a: SomeJavaClass<out String> = 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"
|
||||
}
|
||||
Generated
+6
@@ -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");
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
+6
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user