[FIR] Improve approximation of captured types

- Handle flexible types in recursion check
- Handle intersected supertypes separately
- Make check when not to approximate captured types in type argument
  position more fine-grained.
  Only apply it to case when the captured type is replaced by a star
  projection.
  All other cases are handled by recursive calls to
  approximateCapturedType

#KT-65377 Fixed
This commit is contained in:
Kirill Rakhman
2024-02-08 12:20:43 +01:00
committed by Space Team
parent 831ef0f909
commit b90598823e
8 changed files with 95 additions and 46 deletions
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.types
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator.commonSuperType
import org.jetbrains.kotlin.resolve.calls.inference.hasRecursiveTypeParametersWithGivenSelfType
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.util.concurrent.ConcurrentHashMap
@@ -289,10 +288,45 @@ abstract class AbstractTypeApproximator(
toSuper: Boolean,
depth: Int
): KotlinTypeMarker? {
fun KotlinTypeMarker.replaceRecursionWithStarProjection(capturedType: CapturedTypeMarker, toSuper: Boolean): KotlinTypeMarker {
// This replacement is important for resolving the code like below in K2.
// fun bar(y: FieldOrRef<*>) = y.field
// interface FieldOrRef<FF : AbstractField<FF>> { val field: FF }
// abstract class AbstractField<out F : AbstractField<F>>
// During resolving the value parameter y type, K1 also builds a type for a star projection *.
// See fun TypeParameterDescriptor.starProjectionType(): KotlinType and fun buildStarProjectionTypeByTypeParameters.
// Thanks to it, K1 builds the star projection type as AbstractField<*> and no other approximation is needed.
//
// In turn, K2 never makes such a thing (K2 star projection has no associated type).
// Instead, it resolves y.field as CapturedType(*) C (see usage one line below),
// and the constructor of this captured type has a star projection and a supertype of `AbstractField<C>`.
//
// Without this replacement, the type approximator currently cannot handle such a situation properly
// and builds AbstractField<AbstractField<AbstractField<Any?>>>.
// The check it == type here is intended to find a recursion inside a captured type.
// A similar replacement for baseSubType looks unnecessary, no hits in the tests.
fun TypeArgumentMarker.unwrapForComparison(): KotlinTypeMarker? {
if (this.isStarProjection()) return null
return getType().lowerBoundIfFlexible().originalIfDefinitelyNotNullable()
}
return if (isK2 && toSuper && getArguments().any { it.unwrapForComparison() == capturedType }) {
replaceArguments {
when {
it.unwrapForComparison() != capturedType -> it
// It's possible to use the stub here, because K2 star projection is an object and
// in fact this parameter is never used
else -> createStarProjection(TypeParameterMarkerStubForK2StarProjection)
}
}
} else this
}
val supertypes = capturedType.typeConstructor().supertypes()
val baseSuperType = when (supertypes.size) {
0 -> nullableAnyType() // Let C = in Int, then superType for C and C? is Any?
1 -> supertypes.single()
1 -> supertypes.single().replaceRecursionWithStarProjection(capturedType, toSuper)
// Consider the following example:
// A.getA()::class.java, where `getA()` returns some class from Java
@@ -313,40 +347,14 @@ abstract class AbstractTypeApproximator(
else -> {
val projection = capturedType.typeConstructorProjection()
if (projection.isStarProjection()) intersectTypes(supertypes.toList())
if (projection.isStarProjection()) intersectTypes(supertypes.map { it.replaceRecursionWithStarProjection(capturedType, toSuper) })
else projection.getType()
}
}
val baseSubType = capturedType.lowerType() ?: nothingType()
// This replacement is important for resolving the code like below in K2.
// fun bar(y: FieldOrRef<*>) = y.field
// interface FieldOrRef<FF : AbstractField<FF>> { val field: FF }
// abstract class AbstractField<out F : AbstractField<F>>
// During resolving the value parameter y type, K1 also builds a type for a star projection *.
// See fun TypeParameterDescriptor.starProjectionType(): KotlinType and fun buildStarProjectionTypeByTypeParameters.
// Thanks to it, K1 builds the star projection type as AbstractField<*> and no other approximation is needed.
//
// In turn, K2 never makes such a thing (K2 star projection has no associated type).
// Instead, it resolves y.field as CapturedType(*) C (see usage one line below),
// and the constructor of this captured type has a star projection and a supertype of `AbstractField<C>`.
//
// Without this replacement, the type approximator currently cannot handle such a situation properly
// and builds AbstractField<AbstractField<AbstractField<Any?>>>.
// The check it == type here is intended to find a recursion inside a captured type.
// A similar replacement for baseSubType looks unnecessary, no hits in the tests.
val replacedSuperType = if (isK2 && toSuper && baseSuperType.getArguments().any { it == capturedType }) {
baseSuperType.replaceArguments {
when {
it != capturedType -> it
// It's possible to use the stub here, because K2 star projection is an object and
// in fact this parameter is never used
else -> createStarProjection(TypeParameterMarkerStubForK2StarProjection)
}
}
} else baseSuperType
val approximatedSuperType by lazy(LazyThreadSafetyMode.NONE) {
approximateToSuperType(replacedSuperType, conf, depth)
approximateToSuperType(baseSuperType, conf, depth)
}
val approximatedSubType by lazy(LazyThreadSafetyMode.NONE) { approximateToSubType(baseSubType, conf, depth) }
@@ -364,7 +372,7 @@ abstract class AbstractTypeApproximator(
return null
}
}
val baseResult = if (toSuper) approximatedSuperType ?: replacedSuperType else approximatedSubType ?: baseSubType
val baseResult = if (toSuper) approximatedSuperType ?: baseSuperType else approximatedSubType ?: baseSubType
// C = in Int, Int <: C => Int? <: C?
// C = out Number, C <: Number => C? <: Number?
@@ -508,26 +516,14 @@ abstract class AbstractTypeApproximator(
val capturedType = argumentType.lowerBoundIfFlexible().originalIfDefinitelyNotNullable().asCapturedType()
// When capturing recursive types with self upper bounds, their super types can contain captured types.
// In approximateCapturedType, we check if the super/subtypes of captured types need approximation even if captured types
// themselves don't need approximation, and will land here.
// To support this case, we also don't want to approximate captured types here if the configuration says so.
// TODO rework captured types approximation KT-65228
if (capturedType != null &&
isK2 &&
!conf.capturedType(ctx, capturedType) &&
ctx.hasRecursiveTypeParametersWithGivenSelfType(capturedType.typeConstructor())
) {
continue@loop
}
val capturedStarProjectionOrNull =
capturedType?.typeConstructorProjection()?.takeIf { it.isStarProjection() }
if (capturedStarProjectionOrNull != null &&
(effectiveVariance == TypeVariance.OUT || effectiveVariance == TypeVariance.INV) &&
toSuper &&
capturedType.typeParameter() == parameter
capturedType.typeParameter() == parameter &&
(!isK2 || conf.capturedType(ctx, capturedType))
) {
newArguments[index] = capturedStarProjectionOrNull
continue@loop