Build recursive raw types and raw types which contain type parameters properly

1) Substitute erasure of other type parameters
2) Use star projection at top level for recursive raw types

^KT-46126 Fixed
This commit is contained in:
Victor Petukhov
2021-04-19 23:12:10 +03:00
parent f9d2ca68ce
commit 8dd71ec5c8
35 changed files with 606 additions and 41 deletions
@@ -375,7 +375,7 @@ object AbstractTypeChecker {
val typeVariableConstructor = superArgumentType.typeConstructor() as? TypeVariableTypeConstructorMarker ?: return false
return typeVariableConstructor.typeParameter?.doesFormSelfType(selfConstructor) == true
return typeVariableConstructor.typeParameter?.hasRecursiveBounds(selfConstructor) == true
}
fun AbstractTypeCheckerContext.isSubtypeForSameConstructor(
@@ -506,7 +506,7 @@ object AbstractTypeChecker {
if (subType is CapturedTypeMarker) {
val typeParameter =
context.typeSystemContext.getTypeParameterForArgumentInBaseIfItEqualToTarget(baseType = superType, targetType = subType)
if (typeParameter != null && typeParameter.doesFormSelfType(superType.typeConstructor())) {
if (typeParameter != null && typeParameter.hasRecursiveBounds(superType.typeConstructor())) {
return true
}
}
@@ -313,7 +313,7 @@ interface TypeSystemContext : TypeSystemOptimizationContext {
fun TypeParameterMarker.upperBoundCount(): Int
fun TypeParameterMarker.getUpperBound(index: Int): KotlinTypeMarker
fun TypeParameterMarker.getTypeConstructor(): TypeConstructorMarker
fun TypeParameterMarker.doesFormSelfType(selfConstructor: TypeConstructorMarker): Boolean
fun TypeParameterMarker.hasRecursiveBounds(selfConstructor: TypeConstructorMarker): Boolean
fun areEqualTypeConstructors(c1: TypeConstructorMarker, c2: TypeConstructorMarker): Boolean
@@ -32,8 +32,7 @@ import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.Variance.*
import org.jetbrains.kotlin.types.typeUtil.createProjection
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.utils.sure
private val JAVA_LANG_CLASS_FQ_NAME: FqName = FqName("java.lang.Class")
@@ -210,6 +209,15 @@ class JavaTypeResolver(
val typeParameters = constructor.parameters
if (eraseTypeParameters) {
return typeParameters.map { parameter ->
/*
* We shouldn't erase recursive type parameters to avoid creating types unsatisfying upper bounds.
* E.g. if we got erased raw type of `class Foo<T: Foo<T>> {}` we'd create Foo<(raw) Foo<*>!>!,
* but it's wrong because Foo<*> isn't subtype of Foo<Foo<*>> in accordance with declared upper bound of Foo.
* So we should create Foo<*> in this case (CapturedType(*) is really subtype of Foo<CapturedType(*)>).
*/
if (hasTypeParameterRecursiveBounds(parameter, selfConstructor = null, attr.upperBoundOfTypeParameter))
return@map StarProjectionImpl(parameter)
// Some activity for preventing recursion in cases like `class A<T extends A, F extends T>`
//
// When calculating upper bound of some parameter (attr.upperBoundOfTypeParameter),
@@ -222,12 +230,11 @@ class JavaTypeResolver(
// - Calculating second argument for raw upper bound of T. It depends on F, that again depends on upper bound of T,
// so we get A<*, *>.
// Summary result for upper bound of T is `A<A<*, *>, A<*, *>>..A<out A<*, *>, out A<*, *>>`
val erasedUpperBound =
LazyWrappedType(c.storageManager) {
parameter.getErasedUpperBound(attr.upperBoundOfTypeParameter) {
constructor.declarationDescriptor!!.defaultType.replaceArgumentsWithStarProjections()
}
val erasedUpperBound = LazyWrappedType(c.storageManager) {
parameter.getErasedUpperBound(isRaw, attr) {
constructor.declarationDescriptor!!.defaultType.replaceArgumentsWithStarProjections()
}
}
RawSubstitution.computeProjection(
parameter,
@@ -333,24 +340,51 @@ internal fun TypeParameterDescriptor.getErasedUpperBound(
// Calculation of `potentiallyRecursiveTypeParameter.upperBounds` may recursively depend on `this.getErasedUpperBound`
// E.g. `class A<T extends A, F extends A>`
// To prevent recursive calls return defaultValue() instead
potentiallyRecursiveTypeParameter: TypeParameterDescriptor? = null,
isRaw: Boolean,
typeAttr: JavaTypeAttributes,
defaultValue: (() -> KotlinType) = { ErrorUtils.createErrorType("Can't compute erased upper bound of type parameter `$this`") }
): KotlinType {
if (this === potentiallyRecursiveTypeParameter) return defaultValue()
if (this === typeAttr.upperBoundOfTypeParameter) return defaultValue()
/*
* We should do erasure of containing type parameters with their erasure to avoid creating inconsistent types.
* E.g. for `class Foo<T: Foo<B>, B>`, we'd have erasure for lower bound: Foo<Foo<*>, Any>,
* but it's wrong type: projection(*) != projection(Any).
* So we should substitute erasure of the corresponding type parameter: `Foo<Foo<Any>, Any>` or `Foo<Foo<*>, *>`.
*/
val erasedUpperBounds = defaultType.extractTypeParametersFromUpperBounds(typeAttr.upperBoundOfTypeParameter).associate {
it.typeConstructor to RawSubstitution.computeProjection(
this,
// if erasure happens due to invalid arguments number, use star projections instead
if (isRaw) typeAttr else typeAttr.withFlexibility(INFLEXIBLE),
it.getErasedUpperBound(isRaw, typeAttr)
)
}
val erasedUpperBoundsSubstitutor = TypeSubstitutor.create(TypeConstructorSubstitution.createByConstructorsMap(erasedUpperBounds))
val firstUpperBound = upperBounds.first()
if (firstUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return firstUpperBound.replaceArgumentsWithStarProjections()
return firstUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
OUT_VARIANCE,
typeAttr.upperBoundOfTypeParameter
)
}
val stopAt = potentiallyRecursiveTypeParameter ?: this
val stopAt = typeAttr.upperBoundOfTypeParameter ?: this
var current = firstUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
while (current != stopAt) {
val nextUpperBound = current.upperBounds.first()
if (nextUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return nextUpperBound.replaceArgumentsWithStarProjections()
return nextUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
OUT_VARIANCE,
typeAttr.upperBoundOfTypeParameter
)
}
current = nextUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
@@ -107,10 +107,9 @@ internal object RawSubstitution : TypeSubstitution() {
private val lowerTypeAttr = TypeUsage.COMMON.toAttributes().withFlexibility(JavaTypeFlexibility.FLEXIBLE_LOWER_BOUND)
private val upperTypeAttr = TypeUsage.COMMON.toAttributes().withFlexibility(JavaTypeFlexibility.FLEXIBLE_UPPER_BOUND)
private fun eraseType(type: KotlinType): KotlinType {
val declaration = type.constructor.declarationDescriptor
return when (declaration) {
is TypeParameterDescriptor -> eraseType(declaration.getErasedUpperBound())
private fun eraseType(type: KotlinType, attr: JavaTypeAttributes = JavaTypeAttributes(TypeUsage.COMMON)): KotlinType {
return when (val declaration = type.constructor.declarationDescriptor) {
is TypeParameterDescriptor -> eraseType(declaration.getErasedUpperBound(isRaw = true, attr), attr)
is ClassDescriptor -> {
val declarationForUpper =
type.upperIfFlexible().constructor.declarationDescriptor
@@ -142,7 +141,7 @@ internal object RawSubstitution : TypeSubstitution() {
if (KotlinBuiltIns.isArray(type)) {
val componentTypeProjection = type.arguments[0]
val arguments = listOf(
TypeProjectionImpl(componentTypeProjection.projectionKind, eraseType(componentTypeProjection.type))
TypeProjectionImpl(componentTypeProjection.projectionKind, eraseType(componentTypeProjection.type, attr))
)
return KotlinTypeFactory.simpleType(
type.annotations, type.constructor, arguments, type.isMarkedNullable
@@ -171,7 +170,7 @@ internal object RawSubstitution : TypeSubstitution() {
fun computeProjection(
parameter: TypeParameterDescriptor,
attr: JavaTypeAttributes,
erasedUpperBound: KotlinType = parameter.getErasedUpperBound()
erasedUpperBound: KotlinType = parameter.getErasedUpperBound(isRaw = true, attr)
) = when (attr.flexibility) {
// Raw(List<T>) => (List<Any?>..List<*>)
// Raw(Enum<T>) => (Enum<Enum<*>>..Enum<out Enum<*>>)
@@ -185,7 +185,74 @@ fun KotlinType.contains(predicate: (UnwrappedType) -> Boolean) = TypeUtils.conta
fun KotlinType.replaceArgumentsWithStarProjections() = replaceArgumentsWith(::StarProjectionImpl)
fun KotlinType.replaceArgumentsWithNothing() = replaceArgumentsWith { it.builtIns.nothingType.asTypeProjection() }
private inline fun KotlinType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): KotlinType {
fun KotlinType.extractTypeParametersFromUpperBounds(upperBoundOfTypeParameter: TypeParameterDescriptor?): Set<TypeParameterDescriptor> =
mutableSetOf<TypeParameterDescriptor>().also { extractTypeParametersFromUpperBounds(this, it, upperBoundOfTypeParameter) }
private fun KotlinType.extractTypeParametersFromUpperBounds(
baseType: KotlinType,
to: MutableSet<TypeParameterDescriptor>,
upperBoundOfTypeParameter: TypeParameterDescriptor?
) {
val declarationDescriptor = constructor.declarationDescriptor
if (declarationDescriptor is TypeParameterDescriptor) {
if (constructor != baseType.constructor) {
to += declarationDescriptor
} else {
for (upperBound in declarationDescriptor.upperBounds) {
upperBound.extractTypeParametersFromUpperBounds(baseType, to, upperBoundOfTypeParameter)
}
}
} else {
val typeParameters = (constructor.declarationDescriptor as? ClassifierDescriptorWithTypeParameters)?.declaredTypeParameters
for ((i, argument) in arguments.withIndex()) {
val typeParameter = typeParameters?.get(i)
if (argument.isStarProjection || (typeParameter != null && typeParameter == upperBoundOfTypeParameter)) continue
if (argument.type.constructor.declarationDescriptor in to || argument.type.constructor == baseType.constructor) continue
argument.type.extractTypeParametersFromUpperBounds(baseType, to, upperBoundOfTypeParameter)
}
}
}
fun hasTypeParameterRecursiveBounds(
typeParameter: TypeParameterDescriptor,
selfConstructor: TypeConstructor? = null,
upperBoundOfTypeParameter: TypeParameterDescriptor? = null
): Boolean =
typeParameter.upperBounds.any { upperBound ->
upperBound.containsSelfTypeParameter(typeParameter.defaultType.constructor, upperBoundOfTypeParameter)
&& (selfConstructor == null || upperBound.constructor == selfConstructor)
}
private fun KotlinType.containsSelfTypeParameter(
baseConstructor: TypeConstructor,
upperBoundOfTypeParameter: TypeParameterDescriptor?
): Boolean {
if (this.constructor == baseConstructor) return true
val typeParameters = (constructor.declarationDescriptor as? ClassifierDescriptorWithTypeParameters)?.declaredTypeParameters
return arguments.withIndex().any { (i, argument) ->
val typeParameter = typeParameters?.get(i)
if ((typeParameter != null && typeParameter == upperBoundOfTypeParameter) || argument.isStarProjection) return@any false
argument.type.containsSelfTypeParameter(baseConstructor, upperBoundOfTypeParameter)
}
}
fun KotlinType.replaceArgumentsWithStarProjectionOrMapped(
substitutor: TypeSubstitutor,
substitutionMap: Map<TypeConstructor, TypeProjection>,
variance: Variance,
upperBoundOfTypeParameter: TypeParameterDescriptor?
) =
replaceArgumentsWith { typeParameterDescriptor ->
val argument = arguments.getOrNull(typeParameterDescriptor.index)
if (typeParameterDescriptor != upperBoundOfTypeParameter && argument != null && argument.type.constructor in substitutionMap) {
argument
} else StarProjectionImpl(typeParameterDescriptor)
}.let { substitutor.safeSubstitute(it, variance) }
inline fun KotlinType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): KotlinType {
val unwrapped = unwrap()
return when (unwrapped) {
is FlexibleType -> KotlinTypeFactory.flexibleType(
@@ -196,7 +263,7 @@ private inline fun KotlinType.replaceArgumentsWith(replacement: (TypeParameterDe
}.inheritEnhancement(unwrapped)
}
private inline fun SimpleType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): SimpleType {
inline fun SimpleType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): SimpleType {
if (constructor.parameters.isEmpty() || constructor.declarationDescriptor == null) return this
val newArguments = constructor.parameters.map(replacement)
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typeUtil.hasTypeParameterRecursiveBounds
import org.jetbrains.kotlin.types.typeUtil.representativeUpperBound
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.types.typeUtil.isSignedOrUnsignedNumberType as classicIsSignedOrUnsignedNumberType
@@ -220,11 +221,11 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext, TypeSy
return this.typeConstructor
}
override fun TypeParameterMarker.doesFormSelfType(selfConstructor: TypeConstructorMarker): Boolean {
override fun TypeParameterMarker.hasRecursiveBounds(selfConstructor: TypeConstructorMarker): Boolean {
require(this is TypeParameterDescriptor, this::errorMessage)
require(selfConstructor is TypeConstructor, this::errorMessage)
return doesTypeParameterFormSelfType(this, selfConstructor)
return hasTypeParameterRecursiveBounds(this, selfConstructor)
}
override fun areEqualTypeConstructors(c1: TypeConstructorMarker, c2: TypeConstructorMarker): Boolean {
@@ -21,7 +21,6 @@ import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.calls.inference.wrapWithCapturingSubstitution
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.contains
import org.jetbrains.kotlin.types.typesApproximation.approximateCapturedTypes
import java.util.*
@@ -106,8 +105,3 @@ private fun TypeConstructor.debugInfo() = buildString {
interface NewTypeVariableConstructor {
val originalTypeParameter: TypeParameterDescriptor?
}
fun doesTypeParameterFormSelfType(typeParameter: TypeParameterDescriptor, selfConstructor: TypeConstructor) =
typeParameter.upperBounds.any { upperBound ->
upperBound.contains { it.constructor == typeParameter.typeConstructor } && upperBound.constructor == selfConstructor
}