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:
@@ -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
|
||||
|
||||
|
||||
+46
-12
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user