JVM IR: disable SAM conversion in case type has 'in' projection

... and the corresponding type parameter has a non-trivial (i.e.
non-`Any?`) upper bound.

The best solution here would be to get rid of
`removeExternalProjections` completely, and just use the type of the
argument at the call site, but see KT-52428.

 #KT-51868 Fixed
This commit is contained in:
Alexander Udalov
2022-04-22 00:15:11 +02:00
parent 62c12864f3
commit 584b70719e
14 changed files with 138 additions and 51 deletions
@@ -10,13 +10,17 @@ import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.intersectWrappedTypes
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithNothing
class SamTypeApproximator(builtIns: KotlinBuiltIns, languageVersionSettings: LanguageVersionSettings) {
private val typeApproximator = TypeApproximator(builtIns, languageVersionSettings)
fun getSamTypeForValueParameter(valueParameter: ValueParameterDescriptor): KotlinType? {
fun getSamTypeForValueParameter(
valueParameter: ValueParameterDescriptor,
carefulApproximationOfContravariantProjection: Boolean,
): KotlinType? {
val singleArgumentType: KotlinType
val originalSingleArgumentType: KotlinType?
val varargElementType = valueParameter.varargElementType
@@ -54,14 +58,26 @@ class SamTypeApproximator(builtIns: KotlinBuiltIns, languageVersionSettings: Lan
) ?: originalTypeToUse
approximatedOriginalTypeToUse as KotlinType
return approximatedOriginalTypeToUse.removeExternalProjections()
return approximatedOriginalTypeToUse.removeExternalProjections(carefulApproximationOfContravariantProjection)
}
private fun KotlinType.removeExternalProjections(): KotlinType {
val newArguments = arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
private fun KotlinType.removeExternalProjections(carefulApproximationOfContravariantProjection: Boolean): KotlinType? {
val newArguments = arguments.mapIndexed { i, argument ->
if (carefulApproximationOfContravariantProjection && argument.projectionKind == Variance.IN_VARIANCE) {
// Just erasing `in` from the type projection would lead to an incorrect type for the SAM adapter,
// and error at runtime on JVM if invokedynamic + LambdaMetafactory is used, see KT-51868.
// So we do it "carefully". If we have a class `A<T>` and a method that takes e.g. `A<in String>`, we check
// if `T` has a non-trivial upper bound. If it has one, we don't attempt to perform a SAM conversion at all.
// Otherwise we erase the type to `Any?`, so `A<in String>` becomes `A<Any?>`, which is the computed SAM type.
val parameter = constructor.parameters.getOrNull(i) ?: return null
val upperBound = parameter.upperBounds.singleOrNull()?.upperIfFlexible() ?: return null
if (!upperBound.isNullableAny()) return null
upperBound.asTypeProjection()
} else TypeProjectionImpl(Variance.INVARIANT, argument.type)
}
return replace(newArguments)
}
}
open class SamTypeFactory {
@@ -73,4 +89,4 @@ open class SamTypeFactory {
fun create(originalType: KotlinType): SamType? {
return if (isSamType(originalType)) SamType(originalType) else null
}
}
}