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 89abe82a939..e8001110391 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 @@ -9,25 +9,23 @@ import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirTypeParameter +import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRef import org.jetbrains.kotlin.fir.diagnostics.ConeIntermediateDiagnostic import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind import org.jetbrains.kotlin.fir.java.enhancement.readOnlyToMutable -import org.jetbrains.kotlin.fir.resolve.defaultType -import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents import org.jetbrains.kotlin.fir.resolve.symbolProvider import org.jetbrains.kotlin.fir.resolve.toSymbol -import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.firUnsafe import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag import org.jetbrains.kotlin.fir.symbols.impl.ConeClassLikeLookupTagImpl import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl import org.jetbrains.kotlin.fir.types.jvm.FirJavaTypeRef import org.jetbrains.kotlin.fir.types.jvm.buildJavaTypeRef import org.jetbrains.kotlin.load.java.structure.* -import org.jetbrains.kotlin.load.java.typeEnhancement.TypeComponentPosition import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.StandardClassIds @@ -166,98 +164,6 @@ private fun JavaClassifierType.toConeKotlinTypeWithoutEnhancement( ConeFlexibleType(lowerBound, upperBound) } -private fun computeRawProjection( - session: FirSession, - parameter: FirTypeParameter, - attr: TypeComponentPosition, - erasedUpperBound: ConeKotlinType = parameter.getErasedUpperBound(session) -) = when (attr) { - // Raw(List) => (List..List<*>) - // Raw(Enum) => (Enum>..Enum>) - // In the last case upper bound is equal to star projection `Enum<*>`, - // but we want to keep matching tree structure of flexible bounds (at least they should have the same size) - TypeComponentPosition.FLEXIBLE_LOWER -> { - // T : String -> String - // in T : String -> String - // T : Enum -> Enum<*> - erasedUpperBound - } - TypeComponentPosition.FLEXIBLE_UPPER, TypeComponentPosition.INFLEXIBLE -> { - if (!parameter.variance.allowsOutPosition) - // in T -> Comparable - session.builtinTypes.nothingType.type - else if (erasedUpperBound is ConeClassLikeType && - erasedUpperBound.lookupTag.toSymbol(session)!!.firUnsafe().typeParameters.isNotEmpty() - ) - // T : Enum -> out Enum<*> - ConeKotlinTypeProjectionOut(erasedUpperBound) - else - // T : String -> * - ConeStarProjection - } -} - -// Definition: -// ErasedUpperBound(T : G) = G<*> // UpperBound(T) is a type G 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 FirTypeParameter.getErasedUpperBound( - session: FirSession, - // Calculation of `potentiallyRecursiveTypeParameter.upperBounds` may recursively depend on `this.getErasedUpperBound` - // E.g. `class A` - // To prevent recursive calls return defaultValue() instead - potentiallyRecursiveTypeParameter: FirTypeParameter? = null, - defaultValue: (() -> ConeKotlinType) = { - ConeKotlinErrorType(ConeIntermediateDiagnostic("Can't compute erased upper bound of type parameter `$this`")) - } -): ConeKotlinType { - if (this === potentiallyRecursiveTypeParameter) return defaultValue() - - val firstUpperBound = this.bounds.first().coneType - - return getErasedVersionOfFirstUpperBound(session, firstUpperBound, mutableSetOf(this, potentiallyRecursiveTypeParameter), defaultValue) -} - -private fun getErasedVersionOfFirstUpperBound( - session: FirSession, - firstUpperBound: ConeKotlinType, - alreadyVisitedParameters: MutableSet, - defaultValue: () -> ConeKotlinType -): ConeKotlinType = - when (firstUpperBound) { - is ConeClassLikeType -> - firstUpperBound.withArguments(firstUpperBound.typeArguments.map { ConeStarProjection }.toTypedArray()) - - is ConeFlexibleType -> { - val lowerBound = - getErasedVersionOfFirstUpperBound(session, firstUpperBound.lowerBound, alreadyVisitedParameters, defaultValue) - .lowerBoundIfFlexible() - if (firstUpperBound.upperBound is ConeTypeParameterType) { - // Avoid exponential complexity - ConeFlexibleType( - lowerBound, - lowerBound.withNullability(ConeNullability.NULLABLE, session.inferenceComponents.ctx) - ) - } else { - ConeFlexibleType( - lowerBound, - getErasedVersionOfFirstUpperBound(session, firstUpperBound.upperBound, alreadyVisitedParameters, defaultValue) - ) - } - } - is ConeTypeParameterType -> { - val current = firstUpperBound.lookupTag.typeParameterSymbol.fir - - if (alreadyVisitedParameters.add(current)) { - val nextUpperBound = current.bounds.first().coneType - getErasedVersionOfFirstUpperBound(session, nextUpperBound, alreadyVisitedParameters, defaultValue) - } else { - defaultValue() - } - } - else -> error("Unexpected kind of firstUpperBound: $firstUpperBound [${firstUpperBound::class}]") - } - private fun JavaClassifierType.toConeKotlinTypeForFlexibleBound( session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, @@ -283,17 +189,14 @@ private fun JavaClassifierType.toConeKotlinTypeForFlexibleBound( } val mappedTypeArguments = if (isRaw) { - val defaultArgs = (1..classifier.typeParameters.size).map { ConeStarProjection } - - if (mode == FirJavaTypeConversionMode.TYPE_PARAMETER_BOUND) { - // This is not fully correct, but it's a simple fix for some time to avoid recursive definition: - // to create a proper raw type arguments, we should take class parameters some time + val defaultArgs = Array(classifier.typeParameters.size) { ConeStarProjection } + // This isn't entirely correct, but it prevents infinite recursion in cases like A, + // where the upper bound would be an infinite type `X = A..A<*>?`. + if (lowerBound != null || mode == FirJavaTypeConversionMode.TYPE_PARAMETER_BOUND) { defaultArgs } else { - val position = if (lowerBound == null) TypeComponentPosition.FLEXIBLE_LOWER else TypeComponentPosition.FLEXIBLE_UPPER - val classSymbol = session.symbolProvider.getClassLikeSymbolByFqName(classId) as? FirRegularClassSymbol - classSymbol?.fir?.createRawArguments(session, defaultArgs, position) ?: defaultArgs + classSymbol?.fir?.typeParameters?.eraseToUpperBounds(session, javaTypeParameterStack) ?: defaultArgs } } else { val useTypeParameters = mode != FirJavaTypeConversionMode.TYPE_PARAMETER_BOUND && mode != FirJavaTypeConversionMode.SUPERTYPE @@ -302,14 +205,14 @@ private fun JavaClassifierType.toConeKotlinTypeForFlexibleBound( classSymbol?.fir?.typeParameters } ?: emptyList() - typeArguments.indices.map { index -> + Array(typeArguments.size) { index -> val argument = typeArguments[index] val parameter = typeParameters.getOrNull(index)?.symbol?.fir argument.toConeProjectionWithoutEnhancement(session, javaTypeParameterStack, parameter, mode) } } - lookupTag.constructClassType(mappedTypeArguments.toTypedArray(), isNullable = lowerBound != null, attributes) + lookupTag.constructClassType(mappedTypeArguments, isNullable = lowerBound != null, attributes) } is JavaTypeParameter -> { val symbol = javaTypeParameterStack[classifier] @@ -342,18 +245,49 @@ private fun JavaClassifierType.argumentsMakeSenseOnlyForMutableContainer( return mutableLastParameterVariance != Variance.OUT_VARIANCE } - -private fun FirRegularClass.createRawArguments( - session: FirSession, - defaultArgs: List, - position: TypeComponentPosition -): List = typeParameters.filterIsInstance().map { typeParameter -> - val erasedUpperBound = typeParameter.getErasedUpperBound(session) { - defaultType().withArguments(defaultArgs.toTypedArray()) - } - computeRawProjection(session, typeParameter, position, erasedUpperBound) +private fun List.eraseToUpperBounds( + session: FirSession, javaTypeParameterStack: JavaTypeParameterStack +): Array { + val cache = mutableMapOf() + return Array(size) { index -> this[index].symbol.fir.eraseToUpperBound(session, javaTypeParameterStack, cache) } } +private fun FirTypeParameter.eraseToUpperBound( + session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, + cache: MutableMap +): ConeKotlinType { + return cache.getOrPut(this) { + cache[this] = ConeKotlinErrorType(ConeIntermediateDiagnostic("self-recursive type parameter $name")) // mark to avoid loops + bounds.first().toConeKotlinTypeProbablyFlexible(session, javaTypeParameterStack) + .eraseAsUpperBound(session, javaTypeParameterStack, cache) + } +} + +private fun ConeKotlinType.eraseAsUpperBound( + session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, + 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, javaTypeParameterStack, cache), + upperBound.eraseAsUpperBound(session, javaTypeParameterStack, cache) + ) + is ConeTypeParameterType -> + lookupTag.typeParameterSymbol.fir.eraseToUpperBound(session, javaTypeParameterStack, cache).let { + if (isNullable) it.withNullability(nullability, session.typeContext) else it + } + is ConeDefinitelyNotNullType -> + original.eraseAsUpperBound(session, javaTypeParameterStack, cache) + .makeConeTypeDefinitelyNotNullOrNotNull(session.typeContext) + else -> error("unexpected Java type parameter upper bound kind: $this") + } + private fun JavaType?.toConeProjectionWithoutEnhancement( session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/javaTypeUtils.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/javaTypeUtils.kt index 22ca96bd261..e27c7686921 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/javaTypeUtils.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/enhancement/javaTypeUtils.kt @@ -40,10 +40,11 @@ internal fun ConeKotlinType.enhance(session: FirSession, qualifiers: IndexedJava enhanceConeKotlinType(session, qualifiers, 0, mutableListOf().apply { computeSubtreeSizes(this) }) // The index in the lambda is the position of the type component in a depth-first walk of the tree. -// Example: A, E> - 0<1<2, 3>, 4<5>>. For flexible types, the number of nodes in the lower -// and upper bounds should be the same, and their indices match: (A..C) -> (0<1>..0<1>). -// This function precomputes the size of each subtree so that we can quickly skip to the next -// type argument; e.g. subtreeSizes[1] will give 3 for B, indicating that E is at 1 + 3 = 4. +// Example: A, E> - 0<1<2, 3>, 4<5>>. For flexible types, some arguments in the lower bound +// may be replaced with star projections in the upper bound, but otherwise corresponding arguments +// have the same index: (A, D>..E<*, F>) -> (0<1<2>, 3>..0<1, 3>). This function precomputes +// the size of each subtree so that we can quickly skip to the next type argument; e.g. result[1] will +// give 3 for B, indicating that E is at 1 + 3 = 4. private fun ConeKotlinType.computeSubtreeSizes(result: MutableList): Int { val index = result.size result.add(0) // reserve space at index diff --git a/compiler/testData/diagnostics/tests/platformTypes/rawTypes/interdependentTypeParametersFromKotlin.fir.kt b/compiler/testData/diagnostics/tests/platformTypes/rawTypes/interdependentTypeParametersFromKotlin.fir.kt index f5d744a6336..201392b029a 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/rawTypes/interdependentTypeParametersFromKotlin.fir.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/rawTypes/interdependentTypeParametersFromKotlin.fir.kt @@ -8,5 +8,5 @@ class Foo, P2 : Boo, P3 : Boo, P4 : // FILE: main.kt fun main() { - val x = , Boo<*, *, *>, Boo<*, *, *>, Boo<*, *, *>>..Foo, out Boo<*, *, *>, out Boo<*, *, *>, out Boo<*, *, *>>?!")!>Boo.test1() + val x = , Boo<*, *, *>, Boo<*, *, *>, Boo<*, *, *>>..Foo<*, *, *, *>?!")!>Boo.test1() } diff --git a/compiler/testData/ir/irText/classes/clashingFakeOverrideSignatures.fir.txt b/compiler/testData/ir/irText/classes/clashingFakeOverrideSignatures.fir.txt index 0b25ac69af6..ace2eab8551 100644 --- a/compiler/testData/ir/irText/classes/clashingFakeOverrideSignatures.fir.txt +++ b/compiler/testData/ir/irText/classes/clashingFakeOverrideSignatures.fir.txt @@ -366,7 +366,7 @@ FILE fqName: fileName:/clashingFakeOverrideSignatures.kt overridden: public open fun toString (): kotlin.String [fake_override] declared in .outerFun.LocalDerived $this: VALUE_PARAMETER name: type:kotlin.Any - FUN name:test visibility:local modality:FINAL <> (b:.outerFun.LocalBase, d:.outerFun.LocalDerived, d2:.outerFun.LocalDerived2) returnType:kotlin.Unit + FUN LOCAL_FUNCTION name:test visibility:local modality:FINAL <> (b:.outerFun.LocalBase, d:.outerFun.LocalDerived, d2:.outerFun.LocalDerived2) returnType:kotlin.Unit VALUE_PARAMETER name:b index:0 type:.outerFun.LocalBase VALUE_PARAMETER name:d index:1 type:.outerFun.LocalDerived VALUE_PARAMETER name:d2 index:2 type:.outerFun.LocalDerived2 diff --git a/compiler/testData/ir/irText/types/rawTypeInSignature.fir.kt.txt b/compiler/testData/ir/irText/types/rawTypeInSignature.fir.kt.txt index e46e3865c38..7c2fe65726d 100644 --- a/compiler/testData/ir/irText/types/rawTypeInSignature.fir.kt.txt +++ b/compiler/testData/ir/irText/types/rawTypeInSignature.fir.kt.txt @@ -29,7 +29,7 @@ fun testReturnsRawGenericInv(j: JRaw): @FlexibleNullability GenericInv<*>? { return j.returnsRawGenericInv() } -fun testReturnsRawGenericIn(j: JRaw): @FlexibleNullability GenericIn? { +fun testReturnsRawGenericIn(j: JRaw): @FlexibleNullability GenericIn<*>? { return j.returnsRawGenericIn() } @@ -60,11 +60,11 @@ class KRaw : JRaw { return .#<$$delegate_0>.returnsRawGenericInv() } - override fun takesRawGenericIn(g: @FlexibleNullability GenericIn?) { + override fun takesRawGenericIn(g: @FlexibleNullability GenericIn<*>?) { .#<$$delegate_0>.takesRawGenericIn(g = g) } - override fun returnsRawGenericIn(): @FlexibleNullability GenericIn? { + override fun returnsRawGenericIn(): @FlexibleNullability GenericIn<*>? { return .#<$$delegate_0>.returnsRawGenericIn() } diff --git a/compiler/testData/ir/irText/types/rawTypeInSignature.fir.txt b/compiler/testData/ir/irText/types/rawTypeInSignature.fir.txt index 6d185db4a66..1bfdf37bd03 100644 --- a/compiler/testData/ir/irText/types/rawTypeInSignature.fir.txt +++ b/compiler/testData/ir/irText/types/rawTypeInSignature.fir.txt @@ -65,11 +65,11 @@ FILE fqName: fileName:/rawTypeInSignature.kt RETURN type=kotlin.Nothing from='public final fun testReturnsRawGenericInv (j: .JRaw): @[FlexibleNullability] .GenericInv<*>? declared in ' CALL 'public abstract fun returnsRawGenericInv (): @[FlexibleNullability] .GenericInv<*>? declared in .JRaw' type=@[FlexibleNullability] .GenericInv<*>? origin=null $this: GET_VAR 'j: .JRaw declared in .testReturnsRawGenericInv' type=.JRaw origin=null - FUN name:testReturnsRawGenericIn visibility:public modality:FINAL <> (j:.JRaw) returnType:@[FlexibleNullability] .GenericIn? + FUN name:testReturnsRawGenericIn visibility:public modality:FINAL <> (j:.JRaw) returnType:@[FlexibleNullability] .GenericIn<*>? VALUE_PARAMETER name:j index:0 type:.JRaw BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun testReturnsRawGenericIn (j: .JRaw): @[FlexibleNullability] .GenericIn? declared in ' - CALL 'public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn? declared in .JRaw' type=@[FlexibleNullability] .GenericIn? origin=null + RETURN type=kotlin.Nothing from='public final fun testReturnsRawGenericIn (j: .JRaw): @[FlexibleNullability] .GenericIn<*>? declared in ' + CALL 'public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn<*>? declared in .JRaw' type=@[FlexibleNullability] .GenericIn<*>? origin=null $this: GET_VAR 'j: .JRaw declared in .testReturnsRawGenericIn' type=.JRaw origin=null FUN name:testReturnsRawGenericOut visibility:public modality:FINAL <> (j:.JRaw) returnType:@[FlexibleNullability] .GenericOut<*>? VALUE_PARAMETER name:j index:0 type:.JRaw @@ -122,23 +122,23 @@ FILE fqName: fileName:/rawTypeInSignature.kt CALL 'public abstract fun returnsRawGenericInv (): @[FlexibleNullability] .GenericInv<*>? declared in .JRaw' type=@[FlexibleNullability] .GenericInv<*>? origin=null $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.JRaw visibility:local [final]' type=.JRaw origin=null receiver: GET_VAR ': .KRaw declared in .KRaw.returnsRawGenericInv' type=.KRaw origin=null - FUN DELEGATED_MEMBER name:takesRawGenericIn visibility:public modality:OPEN <> ($this:.KRaw, g:@[FlexibleNullability] .GenericIn?) returnType:kotlin.Unit + FUN DELEGATED_MEMBER name:takesRawGenericIn visibility:public modality:OPEN <> ($this:.KRaw, g:@[FlexibleNullability] .GenericIn<*>?) returnType:kotlin.Unit overridden: - public abstract fun takesRawGenericIn (g: @[FlexibleNullability] .GenericIn?): kotlin.Unit declared in .JRaw + public abstract fun takesRawGenericIn (g: @[FlexibleNullability] .GenericIn<*>?): kotlin.Unit declared in .JRaw $this: VALUE_PARAMETER name: type:.KRaw - VALUE_PARAMETER name:g index:0 type:@[FlexibleNullability] .GenericIn? + VALUE_PARAMETER name:g index:0 type:@[FlexibleNullability] .GenericIn<*>? BLOCK_BODY - CALL 'public abstract fun takesRawGenericIn (g: @[FlexibleNullability] .GenericIn?): kotlin.Unit declared in .JRaw' type=kotlin.Unit origin=null + CALL 'public abstract fun takesRawGenericIn (g: @[FlexibleNullability] .GenericIn<*>?): kotlin.Unit declared in .JRaw' type=kotlin.Unit origin=null $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.JRaw visibility:local [final]' type=.JRaw origin=null receiver: GET_VAR ': .KRaw declared in .KRaw.takesRawGenericIn' type=.KRaw origin=null - g: GET_VAR 'g: @[FlexibleNullability] .GenericIn? declared in .KRaw.takesRawGenericIn' type=@[FlexibleNullability] .GenericIn? origin=null - FUN DELEGATED_MEMBER name:returnsRawGenericIn visibility:public modality:OPEN <> ($this:.KRaw) returnType:@[FlexibleNullability] .GenericIn? + g: GET_VAR 'g: @[FlexibleNullability] .GenericIn<*>? declared in .KRaw.takesRawGenericIn' type=@[FlexibleNullability] .GenericIn<*>? origin=null + FUN DELEGATED_MEMBER name:returnsRawGenericIn visibility:public modality:OPEN <> ($this:.KRaw) returnType:@[FlexibleNullability] .GenericIn<*>? overridden: - public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn? declared in .JRaw + public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn<*>? declared in .JRaw $this: VALUE_PARAMETER name: type:.KRaw BLOCK_BODY - RETURN type=kotlin.Nothing from='public open fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn? declared in .KRaw' - CALL 'public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn? declared in .JRaw' type=@[FlexibleNullability] .GenericIn? origin=null + RETURN type=kotlin.Nothing from='public open fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn<*>? declared in .KRaw' + CALL 'public abstract fun returnsRawGenericIn (): @[FlexibleNullability] .GenericIn<*>? declared in .JRaw' type=@[FlexibleNullability] .GenericIn<*>? origin=null $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.JRaw visibility:local [final]' type=.JRaw origin=null receiver: GET_VAR ': .KRaw declared in .KRaw.returnsRawGenericIn' type=.KRaw origin=null FUN DELEGATED_MEMBER name:takesRawGenericOut visibility:public modality:OPEN <> ($this:.KRaw, g:@[FlexibleNullability] .GenericOut<*>?) returnType:kotlin.Unit