Use upper bound aware type approximator for intersection types inside sam types in contravariant positions to build proper types in terms of subtyping

This commit is contained in:
Victor Petukhov
2021-06-16 18:55:03 +03:00
committed by TeamCityServer
parent 6a78e0a10c
commit 4aeabb6b0f
21 changed files with 424 additions and 18 deletions
@@ -6,14 +6,17 @@
package org.jetbrains.kotlin.backend.common
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.sam.getAbstractMembers
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.intersectWrappedTypes
import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithNothing
class SamType constructor(val type: KotlinType) {
class SamType constructor(val type: KotlinType, val hasUnapproximatableArguments: Boolean = false) {
val classDescriptor: ClassDescriptor
get() = type.constructor.declarationDescriptor as? ClassDescriptor ?: error("Sam/Fun interface not a class descriptor: $type")
@@ -38,7 +41,9 @@ class SamType constructor(val type: KotlinType) {
}
open class SamTypeFactory {
fun createByValueParameter(valueParameter: ValueParameterDescriptor): SamType? {
private lateinit var typeApproximator: TypeApproximator
fun createByValueParameter(valueParameter: ValueParameterDescriptor, languageVersionSettings: LanguageVersionSettings): SamType? {
val singleArgumentType: KotlinType
val originalSingleArgumentType: KotlinType?
val varargElementType = valueParameter.varargElementType
@@ -56,6 +61,11 @@ open class SamTypeFactory {
if (singleArgumentType.isError || originalSingleArgumentType!!.isError) {
return null
}
if (!::typeApproximator.isInitialized) {
typeApproximator = TypeApproximator(singleArgumentType.builtIns, languageVersionSettings)
}
// 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)
@@ -63,20 +73,41 @@ open class SamTypeFactory {
// 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.
val originalTypeToUse =
if (KotlinBuiltIns.isNothing(singleArgumentType))
originalSingleArgumentType.replaceArgumentsWithNothing()
else singleArgumentType
return create(originalTypeToUse.removeExternalProjections())
val originalTypeToUse = if (KotlinBuiltIns.isNothing(singleArgumentType))
originalSingleArgumentType.replaceArgumentsWithNothing()
else singleArgumentType
val approximatedOriginalTypeToUse = typeApproximator.approximateToSubType(
originalTypeToUse,
TypeApproximatorConfiguration.UpperBoundAwareIntersectionTypeApproximator
) ?: originalTypeToUse
approximatedOriginalTypeToUse as KotlinType
val hasUnapproximatableArguments = hasUnapproximatableArguments(approximatedOriginalTypeToUse)
return create((approximatedOriginalTypeToUse).removeExternalProjections(), hasUnapproximatableArguments)
}
/*
* declaration site: `class Foo<T> where T: X, T: Y {}`, X and Y aren't related by subtyping
* use site: Foo<in {X & Y}>
* => `in {X & Y}` is unapproximatable type argument
*/
fun hasUnapproximatableArguments(type: KotlinType) =
type.arguments.isNotEmpty() && type.arguments.withIndex().any { (i, argument) ->
if (argument.projectionKind != Variance.IN_VARIANCE) return@any false
if (argument.type.constructor !is IntersectionTypeConstructor) return@any false
val typeParameter = type.constructor.parameters.getOrNull(i) ?: return@any false
// we have really intersection type as the result => at least there are two upper bounds which aren't related by subtyping
intersectWrappedTypes(typeParameter.upperBounds).constructor is IntersectionTypeConstructor
}
open fun isSamType(type: KotlinType): Boolean {
val descriptor = type.constructor.declarationDescriptor
return descriptor is ClassDescriptor && descriptor.isFun
}
fun create(originalType: KotlinType): SamType? {
return if (isSamType(originalType)) SamType(originalType) else null
@JvmOverloads
fun create(originalType: KotlinType, hasUnapproximatableArguments: Boolean = false): SamType? {
return if (isSamType(originalType)) SamType(originalType, hasUnapproximatableArguments) else null
}
private fun KotlinType.removeExternalProjections(): KotlinType {