diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaTypeConversion.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaTypeConversion.kt index 3a94ae72ca8..d32f5990ce3 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaTypeConversion.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/JavaTypeConversion.kt @@ -224,38 +224,3 @@ private fun JavaClassifierType.argumentsMakeSenseOnlyForMutableContainer( return mutableLastParameterVariance != Variance.OUT_VARIANCE } - -private fun List.eraseToUpperBounds(session: FirSession): Array { - val cache = mutableMapOf() - return Array(size) { index -> this[index].fir.eraseToUpperBound(session, cache) } -} - -private fun FirTypeParameter.eraseToUpperBound(session: FirSession, cache: MutableMap): ConeKotlinType { - return cache.getOrPut(this) { - // Mark to avoid loops. - cache[this] = ConeErrorType(ConeIntermediateDiagnostic("self-recursive type parameter $name")) - // We can assume that Java type parameter bounds are already converted. - symbol.resolvedBounds.first().coneType.eraseAsUpperBound(session, cache) - } -} - -private fun ConeKotlinType.eraseAsUpperBound(session: FirSession, cache: MutableMap): ConeKotlinType = - when (this) { - is ConeClassLikeType -> - withArguments(typeArguments.map { ConeStarProjection }.toTypedArray()) - is ConeFlexibleType -> - // If one bound is a type parameter, the other is probably the same type parameter, - // so there is no exponential complexity here due to cache lookups. - coneFlexibleOrSimpleType( - session.typeContext, - lowerBound.eraseAsUpperBound(session, cache), - upperBound.eraseAsUpperBound(session, cache) - ) - is ConeTypeParameterType -> - lookupTag.typeParameterSymbol.fir.eraseToUpperBound(session, cache).let { - if (isNullable) it.withNullability(nullability, session.typeContext) else it - } - is ConeDefinitelyNotNullType -> - original.eraseAsUpperBound(session, cache).makeConeTypeDefinitelyNotNullOrNotNull(session.typeContext) - else -> error("unexpected Java type parameter upper bound kind: $this") - } diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt index 4e72a6d1ef4..c8b78173e55 100644 --- a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUtils.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass import org.jetbrains.kotlin.fir.declarations.utils.isExpect import org.jetbrains.kotlin.fir.declarations.utils.modality import org.jetbrains.kotlin.fir.declarations.utils.visibility +import org.jetbrains.kotlin.fir.diagnostics.ConeRecursiveTypeParameterDuringErasureError import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap @@ -681,3 +682,94 @@ private fun lowerThanBound(context: ConeInferenceContext, argument: ConeKotlinTy fun KotlinTypeMarker.isSubtypeOf(context: TypeCheckerProviderContext, type: KotlinTypeMarker?): Boolean = type != null && AbstractTypeChecker.isSubtypeOf(context, this, type) + +fun List.eraseToUpperBoundsAssociated( + session: FirSession, + intersectUpperBounds: Boolean = false, + eraseRecursively: Boolean = false +): Map { + val cache = mutableMapOf() + return associateWith { it.fir.eraseToUpperBound(session, cache, intersectUpperBounds, eraseRecursively) } +} + +fun List.eraseToUpperBounds(session: FirSession): Array { + val cache = mutableMapOf() + return Array(size) { index -> + this[index].fir.eraseToUpperBound(session, cache, intersectUpperBounds = false, eraseRecursively = false) + } +} + +private fun FirTypeParameter.eraseToUpperBound( + session: FirSession, + cache: MutableMap, + intersectUpperBounds: Boolean, + eraseRecursively: Boolean +): ConeKotlinType { + fun eraseAsUpperBound(type: FirResolvedTypeRef) = + type.coneType.eraseAsUpperBound(session, cache, intersectUpperBounds, eraseRecursively) + + return cache.getOrPut(this) { + // Mark to avoid loops. + cache[this] = ConeErrorType(ConeRecursiveTypeParameterDuringErasureError(name)) + // We can assume that Java type parameter bounds are already converted. + if (intersectUpperBounds) { + ConeTypeIntersector.intersectTypes(session.typeContext, symbol.resolvedBounds.map(::eraseAsUpperBound)) + } else { + eraseAsUpperBound(symbol.resolvedBounds.first()) + } + } +} + +private fun SimpleTypeMarker.eraseArgumentsDeeply( + typeContext: ConeInferenceContext, + cache: MutableMap, + intersectUpperBounds: Boolean, +): ConeKotlinType = with(typeContext) { + replaceArgumentsDeeply { typeArgument -> + val typeConstructor = typeArgument.getType().typeConstructor().takeIf { it.isTypeParameterTypeConstructor() } + ?: return@replaceArgumentsDeeply typeArgument + + typeConstructor as ConeTypeParameterLookupTag + + val erasedType = typeConstructor.typeParameterSymbol.fir.eraseToUpperBound( + session, cache, intersectUpperBounds, eraseRecursively = true + ) + + if ((erasedType as? ConeErrorType)?.diagnostic is ConeRecursiveTypeParameterDuringErasureError) + return@replaceArgumentsDeeply ConeStarProjection + + erasedType.toTypeProjection(ProjectionKind.OUT) + } as ConeKotlinType +} + +private fun ConeKotlinType.eraseAsUpperBound( + session: FirSession, + cache: MutableMap, + intersectUpperBounds: Boolean, + eraseRecursively: Boolean +): ConeKotlinType = + when (this) { + is ConeClassLikeType -> { + if (eraseRecursively) { + eraseArgumentsDeeply(session.typeContext, cache, intersectUpperBounds) + } else { + withArguments(typeArguments.map { ConeStarProjection }.toTypedArray()) + } + } + is ConeFlexibleType -> + // If one bound is a type parameter, the other is probably the same type parameter, + // so there is no exponential complexity here due to cache lookups. + coneFlexibleOrSimpleType( + session.typeContext, + lowerBound.eraseAsUpperBound(session, cache, intersectUpperBounds, eraseRecursively), + upperBound.eraseAsUpperBound(session, cache, intersectUpperBounds, eraseRecursively) + ) + is ConeTypeParameterType -> + lookupTag.typeParameterSymbol.fir.eraseToUpperBound(session, cache, intersectUpperBounds, eraseRecursively).let { + if (isNullable) it.withNullability(nullability, session.typeContext) else it + } + is ConeDefinitelyNotNullType -> + original.eraseAsUpperBound(session, cache, intersectUpperBounds, eraseRecursively) + .makeConeTypeDefinitelyNotNullOrNotNull(session.typeContext) + else -> error("unexpected Java type parameter upper bound kind: $this") + }