K2: handle recursive types properly in approximation

This commit is intended to deal with inconsistency in K1/K2
star projection handling.
K1 star projection includes a 'type' property.
This type from a star projection can be used for relevant
functions / properties return types,
and already includes some approximation for recursive generics.
In contrast, K2 star projection is an object,
and return types of relevant functions / properties are
represented as captured types.
To prevent recursion in them in recursive generic case,
this commit includes additional replacement of their type arguments.
See more details in added comments.

#KT-65057 Fixed
This commit is contained in:
Mikhail Glukhikh
2024-02-01 15:20:57 +01:00
committed by Space Team
parent c4d6554493
commit 94bcf6d87f
7 changed files with 38 additions and 50 deletions
@@ -318,8 +318,32 @@ abstract class AbstractTypeApproximator(
}
}
val baseSubType = type.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 v ),
// 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 == type }) {
baseSuperType.replaceArguments {
// It's possible to use the stub here, because K2 star projection is an object and
// in fact this parameter is never used
if (it != type) it else createStarProjection(TypeParameterMarkerStubForK2StarProjection)
}
} else baseSuperType
val approximatedSuperType by lazy(LazyThreadSafetyMode.NONE) { approximateToSuperType(baseSuperType, conf, depth) }
val approximatedSuperType by lazy(LazyThreadSafetyMode.NONE) {
approximateToSuperType(replacedSuperType, conf, depth)
}
val approximatedSubType by lazy(LazyThreadSafetyMode.NONE) { approximateToSubType(baseSubType, conf, depth) }
if (!conf.capturedType(ctx, type)) {
@@ -336,7 +360,7 @@ abstract class AbstractTypeApproximator(
return null
}
}
val baseResult = if (toSuper) approximatedSuperType ?: baseSuperType else approximatedSubType ?: baseSubType
val baseResult = if (toSuper) approximatedSuperType ?: replacedSuperType else approximatedSubType ?: baseSubType
// C = in Int, Int <: C => Int? <: C?
// C = out Number, C <: Number => C? <: Number?
@@ -359,6 +383,8 @@ abstract class AbstractTypeApproximator(
}
}
private object TypeParameterMarkerStubForK2StarProjection : TypeParameterMarker
private fun approximateSimpleToSuperType(type: SimpleTypeMarker, conf: TypeApproximatorConfiguration, depth: Int) =
approximateTo(type, conf, toSuper = true, depth = depth)