Introduce TypeParameterUpperBoundEraser to memorize results of type parameters erasion computation

^KT-47785 Fixed
This commit is contained in:
Victor Petukhov
2021-07-21 13:29:44 +03:00
committed by teamcityserver
parent f9cb0d61a8
commit 6706ee87ad
13 changed files with 309 additions and 74 deletions
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.load.java.components.JavaPropertyInitializerEvaluato
import org.jetbrains.kotlin.load.java.components.JavaResolverCache
import org.jetbrains.kotlin.load.java.components.SignaturePropagator
import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver
import org.jetbrains.kotlin.load.java.lazy.types.TypeParameterUpperBoundEraser
import org.jetbrains.kotlin.load.java.sources.JavaSourceElementFactory
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
import org.jetbrains.kotlin.load.java.typeEnhancement.SignatureEnhancement
@@ -41,6 +41,7 @@ class JavaTypeResolver(
private val c: LazyJavaResolverContext,
private val typeParameterResolver: TypeParameterResolver
) {
val typeParameterUpperBoundEraser = TypeParameterUpperBoundEraser()
fun transformJavaType(javaType: JavaType?, attr: JavaTypeAttributes): KotlinType {
return when (javaType) {
@@ -229,9 +230,11 @@ class JavaTypeResolver(
// 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(isRaw, attr) {
constructor.declarationDescriptor!!.defaultType.replaceArgumentsWithStarProjections()
}
typeParameterUpperBoundEraser.getErasedUpperBound(
parameter,
isRaw,
attr.withDefaultType(constructor.declarationDescriptor?.defaultType)
)
}
RawSubstitution.computeProjection(
@@ -310,9 +313,11 @@ data class JavaTypeAttributes(
val flexibility: JavaTypeFlexibility = INFLEXIBLE,
val isForAnnotationParameter: Boolean = false,
// we use it to prevent happening a recursion while compute type parameter's upper bounds
val visitedTypeParameters: Set<TypeParameterDescriptor>? = null
val visitedTypeParameters: Set<TypeParameterDescriptor>? = null,
val defaultType: SimpleType? = null
) {
fun withFlexibility(flexibility: JavaTypeFlexibility) = copy(flexibility = flexibility)
fun withDefaultType(type: SimpleType?) = copy(defaultType = type)
fun withNewVisitedTypeParameter(typeParameter: TypeParameterDescriptor) =
copy(visitedTypeParameters = if (visitedTypeParameters != null) visitedTypeParameters + typeParameter else setOf(typeParameter))
}
@@ -331,70 +336,3 @@ fun TypeUsage.toAttributes(
isForAnnotationParameter = isForAnnotationParameter,
visitedTypeParameters = upperBoundForTypeParameter?.let(::setOf)
)
// Definition:
// ErasedUpperBound(T : G<t>) = G<*> // UpperBound(T) is a type G<t> with arguments
// ErasedUpperBound(T : A) = A // UpperBound(T) is a type A without arguments
// ErasedUpperBound(T : F) = UpperBound(F) // UB(T) is another type parameter F
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
isRaw: Boolean,
typeAttr: JavaTypeAttributes,
defaultValue: (() -> KotlinType) = { ErrorUtils.createErrorType("Can't compute erased upper bound of type parameter `$this`") }
): KotlinType {
val visitedTypeParameters = typeAttr.visitedTypeParameters
if (visitedTypeParameters != null && original in visitedTypeParameters) 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(visitedTypeParameters).associate {
val boundProjection = if (visitedTypeParameters == null || it !in visitedTypeParameters) {
RawSubstitution.computeProjection(
it,
// if erasure happens due to invalid arguments number, use star projections instead
if (isRaw) typeAttr else typeAttr.withFlexibility(INFLEXIBLE),
it.getErasedUpperBound(isRaw, typeAttr.withNewVisitedTypeParameter(this))
)
} else makeStarProjection(it, typeAttr)
it.typeConstructor to boundProjection
}
val erasedUpperBoundsSubstitutor = TypeSubstitutor.create(TypeConstructorSubstitution.createByConstructorsMap(erasedUpperBounds))
val firstUpperBound = upperBounds.first()
if (firstUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return firstUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
OUT_VARIANCE,
typeAttr.visitedTypeParameters
)
}
val stopAt = typeAttr.visitedTypeParameters ?: setOf(this)
var current = firstUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
while (current !in stopAt) {
val nextUpperBound = current.upperBounds.first()
if (nextUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return nextUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
OUT_VARIANCE,
typeAttr.visitedTypeParameters
)
}
current = nextUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
}
return defaultValue()
}
@@ -104,12 +104,15 @@ class RawTypeImpl private constructor(lowerBound: SimpleType, upperBound: Simple
internal object RawSubstitution : TypeSubstitution() {
override fun get(key: KotlinType) = TypeProjectionImpl(eraseType(key))
private val typeParameterUpperBoundEraser = TypeParameterUpperBoundEraser()
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, attr: JavaTypeAttributes = JavaTypeAttributes(TypeUsage.COMMON)): KotlinType {
return when (val declaration = type.constructor.declarationDescriptor) {
is TypeParameterDescriptor -> eraseType(declaration.getErasedUpperBound(isRaw = true, attr), attr)
is TypeParameterDescriptor ->
eraseType(typeParameterUpperBoundEraser.getErasedUpperBound(declaration, isRaw = true, attr), attr)
is ClassDescriptor -> {
val declarationForUpper =
type.upperIfFlexible().constructor.declarationDescriptor
@@ -170,7 +173,7 @@ internal object RawSubstitution : TypeSubstitution() {
fun computeProjection(
parameter: TypeParameterDescriptor,
attr: JavaTypeAttributes,
erasedUpperBound: KotlinType = parameter.getErasedUpperBound(isRaw = true, attr)
erasedUpperBound: KotlinType = typeParameterUpperBoundEraser.getErasedUpperBound(parameter, isRaw = true, attr)
) = when (attr.flexibility) {
// Raw(List<T>) => (List<Any?>..List<*>)
// Raw(Enum<T>) => (Enum<Enum<*>>..Enum<out Enum<*>>)
@@ -0,0 +1,128 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.load.java.lazy.types
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.extractTypeParametersFromUpperBounds
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjectionOrMapped
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
class TypeParameterUpperBoundEraser {
private val storage = LockBasedStorageManager("Type parameter upper bound erasion results")
private val erroneousErasedBound by lazy {
ErrorUtils.createErrorType("Can't compute erased upper bound of type parameter `$this`")
}
private data class DataToEraseUpperBound(
val typeParameter: TypeParameterDescriptor,
val isRaw: Boolean,
val typeAttr: JavaTypeAttributes
) {
override fun equals(other: Any?): Boolean {
if (other !is DataToEraseUpperBound) return false
return other.typeParameter == this.typeParameter
&& other.isRaw == this.isRaw
&& other.typeAttr.flexibility == this.typeAttr.flexibility
&& other.typeAttr.howThisTypeIsUsed == this.typeAttr.howThisTypeIsUsed
&& other.typeAttr.isForAnnotationParameter == this.typeAttr.isForAnnotationParameter
&& other.typeAttr.defaultType == this.typeAttr.defaultType
}
override fun hashCode(): Int {
var result = typeParameter.hashCode()
result += 31 * result + if (isRaw) 1 else 0
result += 31 * result + typeAttr.flexibility.hashCode()
result += 31 * result + typeAttr.howThisTypeIsUsed.hashCode()
result += 31 * result + if (typeAttr.isForAnnotationParameter) 1 else 0
result += 31 * result + typeAttr.defaultType.hashCode()
return result
}
}
private val getErasedUpperBound = storage.createMemoizedFunction<DataToEraseUpperBound, KotlinType> {
with(it) { getErasedUpperBoundInternal(typeParameter, isRaw, typeAttr) }
}
internal fun getErasedUpperBound(
typeParameter: TypeParameterDescriptor,
isRaw: Boolean,
typeAttr: JavaTypeAttributes
) = getErasedUpperBound(DataToEraseUpperBound(typeParameter, isRaw, typeAttr))
private fun getDefaultType(typeAttr: JavaTypeAttributes) =
typeAttr.defaultType?.replaceArgumentsWithStarProjections() ?: erroneousErasedBound
// Definition:
// ErasedUpperBound(T : G<t>) = G<*> // UpperBound(T) is a type G<t> with arguments
// ErasedUpperBound(T : A) = A // UpperBound(T) is a type A without arguments
// ErasedUpperBound(T : F) = UpperBound(F) // UB(T) is another type parameter F
private fun getErasedUpperBoundInternal(
// 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
typeParameter: TypeParameterDescriptor,
isRaw: Boolean,
typeAttr: JavaTypeAttributes
): KotlinType {
val visitedTypeParameters = typeAttr.visitedTypeParameters
if (visitedTypeParameters != null && typeParameter.original in visitedTypeParameters)
return getDefaultType(typeAttr)
/*
* 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 = typeParameter.defaultType.extractTypeParametersFromUpperBounds(visitedTypeParameters).associate {
val boundProjection = if (visitedTypeParameters == null || it !in visitedTypeParameters) {
RawSubstitution.computeProjection(
it,
// if erasure happens due to invalid arguments number, use star projections instead
if (isRaw) typeAttr else typeAttr.withFlexibility(JavaTypeFlexibility.INFLEXIBLE),
getErasedUpperBound(it, isRaw, typeAttr.withNewVisitedTypeParameter(typeParameter))
)
} else makeStarProjection(it, typeAttr)
it.typeConstructor to boundProjection
}
val erasedUpperBoundsSubstitutor = TypeSubstitutor.create(TypeConstructorSubstitution.createByConstructorsMap(erasedUpperBounds))
val firstUpperBound = typeParameter.upperBounds.first()
if (firstUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return firstUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
Variance.OUT_VARIANCE,
typeAttr.visitedTypeParameters
)
}
val stopAt = typeAttr.visitedTypeParameters ?: setOf(this)
var current = firstUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
while (current !in stopAt) {
val nextUpperBound = current.upperBounds.first()
if (nextUpperBound.constructor.declarationDescriptor is ClassDescriptor) {
return nextUpperBound.replaceArgumentsWithStarProjectionOrMapped(
erasedUpperBoundsSubstitutor,
erasedUpperBounds,
Variance.OUT_VARIANCE,
typeAttr.visitedTypeParameters
)
}
current = nextUpperBound.constructor.declarationDescriptor as TypeParameterDescriptor
}
return getDefaultType(typeAttr)
}
}