diff --git a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypes.kt b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypes.kt index 3a435612599..7a037eb3467 100644 --- a/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypes.kt +++ b/compiler/fir/cones/src/org/jetbrains/kotlin/fir/types/ConeTypes.kt @@ -119,7 +119,7 @@ abstract class ConeAbbreviatedType : ConeClassLikeType() { abstract val abbreviationLookupTag: ConeClassLikeLookupTag } -data class ConeFlexibleType(val lowerBound: ConeKotlinType, val upperBound: ConeKotlinType) : ConeKotlinType(), +open class ConeFlexibleType(val lowerBound: ConeKotlinType, val upperBound: ConeKotlinType) : ConeKotlinType(), FlexibleTypeMarker { init { @@ -133,6 +133,25 @@ data class ConeFlexibleType(val lowerBound: ConeKotlinType, val upperBound: Cone override val nullability: ConeNullability get() = lowerBound.nullability.takeIf { it == upperBound.nullability } ?: ConeNullability.UNKNOWN + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ConeFlexibleType + + if (lowerBound != other.lowerBound) return false + if (upperBound != other.upperBound) return false + + return true + } + + override fun hashCode(): Int { + var result = lowerBound.hashCode() + result = 31 * result + upperBound.hashCode() + return result + } + } fun ConeKotlinType.upperBoundIfFlexible() = (this as? ConeFlexibleType)?.upperBound ?: this @@ -182,6 +201,8 @@ class ConeDefinitelyNotNullType private constructor(val original: ConeKotlinType } } +class ConeRawType(lowerBound: ConeKotlinType, upperBound: ConeKotlinType) : ConeFlexibleType(lowerBound, upperBound), RawTypeMarker + /* * Contract of the intersection type: it is flat. It means that * intersection type can not contains another intersection types 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 dbe599ffc9b..65d90c36656 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 @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.fir.java.types.FirJavaTypeRef import org.jetbrains.kotlin.fir.references.impl.FirResolvedNamedReferenceImpl import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference import org.jetbrains.kotlin.fir.resolve.* +import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.firUnsafe import org.jetbrains.kotlin.fir.symbols.ConeClassLikeLookupTag import org.jetbrains.kotlin.fir.symbols.ConeClassifierLookupTag import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag @@ -88,7 +89,10 @@ private fun JavaType?.enhancePossiblyFlexible( session, javaTypeParameterStack, annotations, arguments, TypeComponentPosition.FLEXIBLE_UPPER, qualifiers, index ) - coneFlexibleOrSimpleType(session, lowerResult, upperResult) + when { + type.isRaw -> ConeRawType(lowerResult, upperResult) + else -> coneFlexibleOrSimpleType(session, lowerResult, upperResult) + } } is JavaArrayType -> { val baseEnhanced = type.toNotNullConeKotlinType(session, javaTypeParameterStack) @@ -160,6 +164,77 @@ private fun ClassId.mutableToReadOnly(): ClassId? { } } + + +// 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( + // 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("Can't compute erased upper bound of type parameter `$this`") } +): ConeKotlinType { + if (this === potentiallyRecursiveTypeParameter) return defaultValue() + + val firstUpperBound = this.bounds.first().coneTypeUnsafe() + + val firstUpperBoundClassifier = firstUpperBound + if (firstUpperBoundClassifier is ConeClassLikeType) { + return firstUpperBound.withArguments(firstUpperBound.typeArguments.map { ConeStarProjection }.toTypedArray()) + } + + val stopAt = potentiallyRecursiveTypeParameter ?: this + var current = (firstUpperBound as ConeTypeParameterType).lookupTag.typeParameterSymbol.fir + + while (current != stopAt) { + + val nextUpperBound = current.bounds.first().coneTypeUnsafe() + if (nextUpperBound is ConeClassLikeType) { + return nextUpperBound.withArguments(nextUpperBound.typeArguments.map { ConeStarProjection }.toTypedArray()) + } + + current = (nextUpperBound as ConeTypeParameterType).lookupTag.typeParameterSymbol.fir + } + + return defaultValue() +} + + +fun computeProjection( + session: FirSession, + parameter: FirTypeParameter, + attr: TypeComponentPosition, + erasedUpperBound: ConeKotlinType = parameter.getErasedUpperBound() +) = 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 + } +} + + + private fun JavaClassifierType.enhanceInflexibleType( session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, @@ -169,7 +244,8 @@ private fun JavaClassifierType.enhanceInflexibleType( qualifiers: IndexedJavaTypeQualifiers, index: Int ): ConeLookupTagBasedType { - val originalTag = when (val classifier = classifier) { + val classifier = classifier + val originalTag = when (classifier) { is JavaClass -> { val classId = classifier.classId!! var mappedId = JavaToKotlinClassMap.mapJavaToKotlin(classId.asSingleFqName()) @@ -188,20 +264,32 @@ private fun JavaClassifierType.enhanceInflexibleType( val effectiveQualifiers = qualifiers(index) val enhancedTag = originalTag.enhanceMutability(effectiveQualifiers, position) - var globalArgIndex = index + 1 - val enhancedArguments = arguments.mapIndexed { localArgIndex, arg -> - if (arg is JavaWildcardType) { - globalArgIndex++ - arg.toConeProjection( - session, - javaTypeParameterStack, - ((originalTag as? FirBasedSymbol<*>)?.fir as? FirCallableMemberDeclaration<*>)?.typeParameters?.getOrNull(localArgIndex) - ) - } else { - val argEnhancedTypeRef = arg.enhancePossiblyFlexible(session, javaTypeParameterStack, annotations, qualifiers, globalArgIndex) - globalArgIndex += arg.subtreeSize() - // For arg == null (raw type) we take to match everything - argEnhancedTypeRef.type.type.toTypeProjection(if (arg == null) Variance.OUT_VARIANCE else Variance.INVARIANT) + val enhancedArguments = if (isRaw) { + val firClassifier = originalTag.toSymbol(session)!!.firUnsafe() + firClassifier.typeParameters.map { + val fir = it + val erasedUpperBound = fir.getErasedUpperBound { + firClassifier.defaultType().withArguments(firClassifier.typeParameters.map { ConeStarProjection }.toTypedArray()) + } + computeProjection(session, fir, position, erasedUpperBound) + } + } else { + var globalArgIndex = index + 1 + arguments.mapIndexed { localArgIndex, arg -> + if (arg is JavaWildcardType) { + globalArgIndex++ + arg.toConeProjection( + session, + javaTypeParameterStack, + ((originalTag as? FirBasedSymbol<*>)?.fir as? FirCallableMemberDeclaration<*>)?.typeParameters?.getOrNull(localArgIndex) + ) + } else { + val argEnhancedTypeRef = + arg.enhancePossiblyFlexible(session, javaTypeParameterStack, annotations, qualifiers, globalArgIndex) + globalArgIndex += arg.subtreeSize() + + argEnhancedTypeRef.type.type.toTypeProjection(Variance.INVARIANT) + } } } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt index 5daae27763b..fe348758d72 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/ScopeUtils.kt @@ -33,6 +33,7 @@ fun ConeKotlinType.scope(useSiteSession: FirSession, scopeSession: ScopeSession) } ) } + is ConeRawType -> lowerBound.scope(useSiteSession, scopeSession) is ConeFlexibleType -> lowerBound.scope(useSiteSession, scopeSession) is ConeIntersectionType -> FirCompositeScope( intersectedTypes.mapNotNullTo(mutableListOf()) { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt index 73c226a2d40..2d7514c8878 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt @@ -91,8 +91,8 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty } override fun FlexibleTypeMarker.asRawType(): RawTypeMarker? { - assert(this is ConeKotlinType) - return null // TODO + require(this is ConeFlexibleType) + return this as? ConeRawType } override fun FlexibleTypeMarker.upperBound(): SimpleTypeMarker {