FIR: move Java type conversion functions to a separate file

This commit is contained in:
pyos
2021-08-12 13:14:19 +02:00
committed by teamcityserver
parent 9cb6b75665
commit 74b3634e1f
2 changed files with 441 additions and 424 deletions
@@ -0,0 +1,440 @@
/*
* 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.fir.java
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.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.types.*
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
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
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.runIf
private fun ClassId.toLookupTag(): ConeClassLikeLookupTag {
return ConeClassLikeLookupTagImpl(this)
}
internal fun ClassId.toConeKotlinType(
typeArguments: Array<ConeTypeProjection>,
isNullable: Boolean,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeLookupTagBasedType {
return ConeClassLikeTypeImpl(toLookupTag(), typeArguments, isNullable, attributes)
}
internal fun FirTypeRef.toConeKotlinTypeProbablyFlexible(
session: FirSession, javaTypeParameterStack: JavaTypeParameterStack
): ConeKotlinType =
when (this) {
is FirResolvedTypeRef -> type
is FirJavaTypeRef -> type.toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = false)
else -> ConeKotlinErrorType(
ConeSimpleDiagnostic("Unexpected type reference in JavaClassUseSiteMemberScope: ${this::class.java}", DiagnosticKind.Java)
)
}
internal fun JavaType.toFirJavaTypeRef(session: FirSession, javaTypeParameterStack: JavaTypeParameterStack): FirJavaTypeRef {
return buildJavaTypeRef {
annotationBuilder = {
(this@toFirJavaTypeRef as? JavaClassifierType)?.annotations.orEmpty().map {
it.toFirAnnotationCall(session, javaTypeParameterStack)
}
}
type = this@toFirJavaTypeRef
}
}
internal fun JavaClassifierType.toFirResolvedTypeRef(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
isForSupertypes: Boolean,
forTypeParameterBounds: Boolean
): FirResolvedTypeRef {
val coneType =
if (isForSupertypes)
toConeKotlinTypeForFlexibleBound(session, javaTypeParameterStack, forTypeParameterBounds, isForSupertypes)
else
toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, forTypeParameterBounds, isForSupertypes)
return buildResolvedTypeRef {
type = coneType
this@toFirResolvedTypeRef.annotations.mapTo(annotations) { it.toFirAnnotationCall(session, javaTypeParameterStack) }
}
}
internal fun JavaType?.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forAnnotationMember: Boolean = false,
isForSupertypes: Boolean = false
): ConeKotlinType {
val attributes = if (this != null && annotations.isNotEmpty()) {
ConeAttributes.create(
listOf(CustomAnnotationTypeAttribute(annotations.map { it.toFirAnnotationCall(session, javaTypeParameterStack) }))
)
} else {
ConeAttributes.Empty
}
return when (this) {
is JavaClassifierType -> {
toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
}
is JavaPrimitiveType -> {
val primitiveType = type
val kotlinPrimitiveName = when (val javaName = primitiveType?.typeName?.asString()) {
null -> "Unit"
else -> javaName.capitalizeAsciiOnly()
}
val classId = StandardClassIds.byName(kotlinPrimitiveName)
classId.toConeKotlinType(emptyArray(), isNullable = false, attributes)
}
is JavaArrayType -> {
toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
forAnnotationMember,
isForSupertypes,
attributes = attributes
)
}
is JavaWildcardType ->
bound?.toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
isForSupertypes = isForSupertypes
) ?: StandardClassIds.Any.toConeFlexibleType(emptyArray(), attributes = attributes)
null ->
StandardClassIds.Any.toConeFlexibleType(emptyArray(), attributes = attributes)
else -> error("Strange JavaType: ${this::class.java}")
}
}
private fun JavaArrayType.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forAnnotationValueParameter: Boolean = false,
isForSupertypes: Boolean,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeKotlinType {
val componentType = componentType
return if (componentType !is JavaPrimitiveType) {
val classId = StandardClassIds.Array
val argumentType = componentType.toConeKotlinTypeWithoutEnhancement(
session, javaTypeParameterStack, forAnnotationValueParameter, isForSupertypes
)
if (forAnnotationValueParameter) {
classId.constructClassLikeType(arrayOf(argumentType), isNullable = false, attributes = attributes)
} else {
classId.toConeFlexibleType(
arrayOf(argumentType),
typeArgumentsForUpper = arrayOf(ConeKotlinTypeProjectionOut(argumentType)),
attributes = attributes
)
}
} else {
val javaComponentName = componentType.type?.typeName?.asString()?.capitalizeAsciiOnly() ?: error("Array of voids")
val classId = StandardClassIds.byName(javaComponentName + "Array")
if (forAnnotationValueParameter) {
classId.constructClassLikeType(emptyArray(), isNullable = false, attributes = attributes)
} else {
classId.toConeFlexibleType(emptyArray(), attributes = attributes)
}
}
}
private fun ClassId.toConeFlexibleType(
typeArguments: Array<ConeTypeProjection>,
typeArgumentsForUpper: Array<ConeTypeProjection> = typeArguments,
attributes: ConeAttributes = ConeAttributes.Empty
) = ConeFlexibleType(
toConeKotlinType(typeArguments, isNullable = false, attributes),
toConeKotlinType(typeArgumentsForUpper, isNullable = true, attributes)
)
private fun JavaClassifierType.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forTypeParameterBounds: Boolean = false,
isForSupertypes: Boolean = false,
forAnnotationMember: Boolean = false,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeKotlinType {
val lowerBound = toConeKotlinTypeForFlexibleBound(
session,
javaTypeParameterStack,
forTypeParameterBounds,
isForSupertypes,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
if (forAnnotationMember) {
return lowerBound
}
val upperBound =
toConeKotlinTypeForFlexibleBound(
session,
javaTypeParameterStack,
forTypeParameterBounds,
isForSupertypes,
lowerBound,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
return if (isRaw)
ConeRawType(lowerBound, upperBound)
else
ConeFlexibleType(lowerBound, upperBound)
}
private fun computeRawProjection(
session: FirSession,
parameter: FirTypeParameter,
attr: TypeComponentPosition,
erasedUpperBound: ConeKotlinType = parameter.getErasedUpperBound(session)
) = when (attr) {
// Raw(List<T>) => (List<Any?>..List<*>)
// Raw(Enum<T>) => (Enum<Enum<*>>..Enum<out 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<T> -> Enum<*>
erasedUpperBound
}
TypeComponentPosition.FLEXIBLE_UPPER, TypeComponentPosition.INFLEXIBLE -> {
if (!parameter.variance.allowsOutPosition)
// in T -> Comparable<Nothing>
session.builtinTypes.nothingType.type
else if (erasedUpperBound is ConeClassLikeType &&
erasedUpperBound.lookupTag.toSymbol(session)!!.firUnsafe<FirRegularClass>().typeParameters.isNotEmpty()
)
// T : Enum<E> -> out Enum<*>
ConeKotlinTypeProjectionOut(erasedUpperBound)
else
// T : String -> *
ConeStarProjection
}
}
// 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 FirTypeParameter.getErasedUpperBound(
session: FirSession,
// 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: 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<FirTypeParameter?>,
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,
forTypeParameterBounds: Boolean,
isForSupertypes: Boolean,
lowerBound: ConeLookupTagBasedType? = null,
forAnnotationMember: Boolean = false,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeLookupTagBasedType {
return when (val classifier = classifier) {
is JavaClass -> {
//val classId = classifier.classId!!
var classId = if (forAnnotationMember) {
JavaToKotlinClassMap.mapJavaToKotlinIncludingClassMapping(classifier.fqName!!)
} else {
JavaToKotlinClassMap.mapJavaToKotlin(classifier.fqName!!)
} ?: classifier.classId!!
if (lowerBound == null || argumentsMakeSenseOnlyForMutableContainer(classId, session)) {
classId = classId.readOnlyToMutable() ?: classId
}
val lookupTag = ConeClassLikeLookupTagImpl(classId)
if (lookupTag == lowerBound?.lookupTag && !isRaw) {
return lookupTag.constructClassType(lowerBound.typeArguments, isNullable = true, attributes)
}
val mappedTypeArguments = if (isRaw) {
val defaultArgs = (1..classifier.typeParameters.size).map { ConeStarProjection }
if (forTypeParameterBounds) {
// 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
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
}
} else {
val typeParameters = runIf(!forTypeParameterBounds && !isForSupertypes) {
val classSymbol = session.symbolProvider.getClassLikeSymbolByFqName(classId) as? FirRegularClassSymbol
classSymbol?.fir?.typeParameters
} ?: emptyList()
typeArguments.indices.map { index ->
val argument = typeArguments[index]
val parameter = typeParameters.getOrNull(index)?.symbol?.fir
argument.toConeProjectionWithoutEnhancement(
session, javaTypeParameterStack, boundTypeParameter = parameter, isForSupertypes = isForSupertypes
)
}
}
lookupTag.constructClassType(mappedTypeArguments.toTypedArray(), isNullable = lowerBound != null, attributes)
}
is JavaTypeParameter -> {
val symbol = javaTypeParameterStack[classifier]
ConeTypeParameterTypeImpl(symbol.toLookupTag(), isNullable = lowerBound != null, attributes)
}
null -> {
val classId = ClassId.topLevel(FqName(this.classifierQualifiedName))
classId.constructClassLikeType(emptyArray(), isNullable = lowerBound != null, attributes)
}
else -> ConeKotlinErrorType(ConeSimpleDiagnostic("Unexpected classifier: $classifier", DiagnosticKind.Java))
}
}
// Returns true for covariant read-only container that has mutable pair with invariant parameter
// List<in A> does not make sense, but MutableList<in A> does
// Same for Map<K, in V>
// But both Iterable<in A>, MutableIterable<in A> don't make sense as they are covariant, so return false
private fun JavaClassifierType.argumentsMakeSenseOnlyForMutableContainer(
classId: ClassId,
session: FirSession,
): Boolean {
if (!JavaToKotlinClassMap.isReadOnly(classId.asSingleFqName().toUnsafe())) return false
val mutableClassId = classId.readOnlyToMutable() ?: return false
if (!typeArguments.lastOrNull().isSuperWildcard()) return false
val mutableLastParameterVariance =
(mutableClassId.toLookupTag().toSymbol(session)?.fir as? FirRegularClass)?.typeParameters?.lastOrNull()?.symbol?.fir?.variance
?: return false
return mutableLastParameterVariance != Variance.OUT_VARIANCE
}
private fun FirRegularClass.createRawArguments(
session: FirSession,
defaultArgs: List<ConeStarProjection>,
position: TypeComponentPosition
): List<ConeTypeProjection> = typeParameters.filterIsInstance<FirTypeParameter>().map { typeParameter ->
val erasedUpperBound = typeParameter.getErasedUpperBound(session) {
defaultType().withArguments(defaultArgs.toTypedArray())
}
computeRawProjection(session, typeParameter, position, erasedUpperBound)
}
private fun JavaType?.toConeProjectionWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
boundTypeParameter: FirTypeParameter?,
isForSupertypes: Boolean = false
): ConeTypeProjection {
return when (this) {
null -> ConeStarProjection
is JavaWildcardType -> {
val bound = this.bound
val argumentVariance = if (isExtends) Variance.OUT_VARIANCE else Variance.IN_VARIANCE
val parameterVariance = boundTypeParameter?.variance ?: Variance.INVARIANT
if (bound == null || parameterVariance != Variance.INVARIANT && parameterVariance != argumentVariance) {
ConeStarProjection
} else {
val boundType = bound.toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = isForSupertypes)
if (argumentVariance == Variance.OUT_VARIANCE) {
ConeKotlinTypeProjectionOut(boundType)
} else {
ConeKotlinTypeProjectionIn(boundType)
}
}
}
else -> toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = isForSupertypes)
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.fir.java
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.*
@@ -14,54 +13,37 @@ import org.jetbrains.kotlin.fir.builder.FirAnnotationContainerBuilder
import org.jetbrains.kotlin.fir.builder.FirBuilderDsl
import org.jetbrains.kotlin.fir.declarations.FirConstructor
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
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.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.*
import org.jetbrains.kotlin.fir.java.declarations.FirJavaClass
import org.jetbrains.kotlin.fir.java.declarations.buildJavaValueParameter
import org.jetbrains.kotlin.fir.java.enhancement.readOnlyToMutable
import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildResolvedNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirReferencePlaceholderForResolvedAnnotations
import org.jetbrains.kotlin.fir.resolve.bindSymbolToLookupTag
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedReferenceError
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
import org.jetbrains.kotlin.fir.resolve.providers.getClassDeclaredPropertySymbols
import org.jetbrains.kotlin.fir.resolve.symbolProvider
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.expectedConeType
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.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
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.structure.impl.JavaElementImpl
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.Name
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.types.ConstantValueKind
import org.jetbrains.kotlin.types.Variance.*
import org.jetbrains.kotlin.util.capitalizeDecapitalize.capitalizeAsciiOnly
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.lang.Deprecated
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.Target
import java.util.*
import java.util.EnumSet
internal val JavaModifierListOwner.modality: Modality
get() = when {
@@ -86,384 +68,6 @@ internal val JavaClass.classKind: ClassKind
else -> ClassKind.CLASS
}
internal fun ClassId.toLookupTag(): ConeClassLikeLookupTag {
return ConeClassLikeLookupTagImpl(this)
}
internal fun ClassId.toConeKotlinType(
typeArguments: Array<ConeTypeProjection>,
isNullable: Boolean,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeLookupTagBasedType {
return ConeClassLikeTypeImpl(toLookupTag(), typeArguments, isNullable, attributes)
}
internal fun FirTypeRef.toConeKotlinTypeProbablyFlexible(
session: FirSession, javaTypeParameterStack: JavaTypeParameterStack
): ConeKotlinType =
when (this) {
is FirResolvedTypeRef -> type
is FirJavaTypeRef -> {
type.toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = false)
}
else -> ConeKotlinErrorType(
ConeSimpleDiagnostic("Unexpected type reference in JavaClassUseSiteMemberScope: ${this::class.java}", DiagnosticKind.Java)
)
}
internal fun JavaType.toFirJavaTypeRef(session: FirSession, javaTypeParameterStack: JavaTypeParameterStack): FirJavaTypeRef {
return buildJavaTypeRef {
annotationBuilder = {
(this@toFirJavaTypeRef as? JavaClassifierType)?.annotations.orEmpty().map {
it.toFirAnnotationCall(session, javaTypeParameterStack)
}
}
type = this@toFirJavaTypeRef
}
}
internal fun JavaClassifierType.toFirResolvedTypeRef(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
isForSupertypes: Boolean,
forTypeParameterBounds: Boolean
): FirResolvedTypeRef {
val coneType =
if (isForSupertypes)
toConeKotlinTypeForFlexibleBound(session, javaTypeParameterStack, forTypeParameterBounds, isForSupertypes)
else
toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, forTypeParameterBounds, isForSupertypes)
return buildResolvedTypeRef {
type = coneType
this@toFirResolvedTypeRef.annotations.mapTo(annotations) { it.toFirAnnotationCall(session, javaTypeParameterStack) }
}
}
internal fun JavaType?.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forAnnotationMember: Boolean = false,
isForSupertypes: Boolean = false
): ConeKotlinType {
val attributes = if (this != null && annotations.isNotEmpty()) {
ConeAttributes.create(
listOf(CustomAnnotationTypeAttribute(annotations.map { it.toFirAnnotationCall(session, javaTypeParameterStack) }))
)
} else {
ConeAttributes.Empty
}
return when (this) {
is JavaClassifierType -> {
toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
}
is JavaPrimitiveType -> {
val primitiveType = type
val kotlinPrimitiveName = when (val javaName = primitiveType?.typeName?.asString()) {
null -> "Unit"
else -> javaName.capitalizeAsciiOnly()
}
val classId = StandardClassIds.byName(kotlinPrimitiveName)
classId.toConeKotlinType(emptyArray(), isNullable = false, attributes)
}
is JavaArrayType -> {
toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
forAnnotationMember,
isForSupertypes,
attributes = attributes
)
}
is JavaWildcardType ->
bound?.toConeKotlinTypeWithoutEnhancement(
session,
javaTypeParameterStack,
isForSupertypes = isForSupertypes
) ?: StandardClassIds.Any.toConeFlexibleType(emptyArray(), attributes = attributes)
null ->
StandardClassIds.Any.toConeFlexibleType(emptyArray(), attributes = attributes)
else -> error("Strange JavaType: ${this::class.java}")
}
}
private fun JavaArrayType.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forAnnotationValueParameter: Boolean = false,
isForSupertypes: Boolean,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeKotlinType {
val componentType = componentType
return if (componentType !is JavaPrimitiveType) {
val classId = StandardClassIds.Array
val argumentType = componentType.toConeKotlinTypeWithoutEnhancement(
session, javaTypeParameterStack, forAnnotationValueParameter, isForSupertypes
)
if (forAnnotationValueParameter) {
classId.constructClassLikeType(arrayOf(argumentType), isNullable = false, attributes = attributes)
} else {
classId.toConeFlexibleType(
arrayOf(argumentType),
typeArgumentsForUpper = arrayOf(ConeKotlinTypeProjectionOut(argumentType)),
attributes = attributes
)
}
} else {
val javaComponentName = componentType.type?.typeName?.asString()?.capitalizeAsciiOnly() ?: error("Array of voids")
val classId = StandardClassIds.byName(javaComponentName + "Array")
if (forAnnotationValueParameter) {
classId.constructClassLikeType(emptyArray(), isNullable = false, attributes = attributes)
} else {
classId.toConeFlexibleType(emptyArray(), attributes = attributes)
}
}
}
private fun ClassId.toConeFlexibleType(
typeArguments: Array<ConeTypeProjection>,
typeArgumentsForUpper: Array<ConeTypeProjection> = typeArguments,
attributes: ConeAttributes = ConeAttributes.Empty
) = ConeFlexibleType(
toConeKotlinType(typeArguments, isNullable = false, attributes),
toConeKotlinType(typeArgumentsForUpper, isNullable = true, attributes)
)
private fun JavaClassifierType.toConeKotlinTypeWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
forTypeParameterBounds: Boolean = false,
isForSupertypes: Boolean = false,
forAnnotationMember: Boolean = false,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeKotlinType {
val lowerBound = toConeKotlinTypeForFlexibleBound(
session,
javaTypeParameterStack,
forTypeParameterBounds,
isForSupertypes,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
if (forAnnotationMember) {
return lowerBound
}
val upperBound =
toConeKotlinTypeForFlexibleBound(
session,
javaTypeParameterStack,
forTypeParameterBounds,
isForSupertypes,
lowerBound,
forAnnotationMember = forAnnotationMember,
attributes = attributes
)
return if (isRaw)
ConeRawType(lowerBound, upperBound)
else
ConeFlexibleType(lowerBound, upperBound)
}
private fun computeRawProjection(
session: FirSession,
parameter: FirTypeParameter,
attr: TypeComponentPosition,
erasedUpperBound: ConeKotlinType = parameter.getErasedUpperBound(session)
) = when (attr) {
// Raw(List<T>) => (List<Any?>..List<*>)
// Raw(Enum<T>) => (Enum<Enum<*>>..Enum<out 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<T> -> Enum<*>
erasedUpperBound
}
TypeComponentPosition.FLEXIBLE_UPPER, TypeComponentPosition.INFLEXIBLE -> {
if (!parameter.variance.allowsOutPosition)
// in T -> Comparable<Nothing>
session.builtinTypes.nothingType.type
else if (erasedUpperBound is ConeClassLikeType &&
erasedUpperBound.lookupTag.toSymbol(session)!!.firUnsafe<FirRegularClass>().typeParameters.isNotEmpty()
)
// T : Enum<E> -> out Enum<*>
ConeKotlinTypeProjectionOut(erasedUpperBound)
else
// T : String -> *
ConeStarProjection
}
}
// 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 FirTypeParameter.getErasedUpperBound(
session: FirSession,
// 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: 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<FirTypeParameter?>,
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,
forTypeParameterBounds: Boolean,
isForSupertypes: Boolean,
lowerBound: ConeLookupTagBasedType? = null,
forAnnotationMember: Boolean = false,
attributes: ConeAttributes = ConeAttributes.Empty
): ConeLookupTagBasedType {
return when (val classifier = classifier) {
is JavaClass -> {
//val classId = classifier.classId!!
var classId = if (forAnnotationMember) {
JavaToKotlinClassMap.mapJavaToKotlinIncludingClassMapping(classifier.fqName!!)
} else {
JavaToKotlinClassMap.mapJavaToKotlin(classifier.fqName!!)
} ?: classifier.classId!!
if (lowerBound == null || argumentsMakeSenseOnlyForMutableContainer(classId, session)) {
classId = classId.readOnlyToMutable() ?: classId
}
val lookupTag = ConeClassLikeLookupTagImpl(classId)
if (lookupTag == lowerBound?.lookupTag && !isRaw) {
return lookupTag.constructClassType(lowerBound.typeArguments, isNullable = true, attributes)
}
val mappedTypeArguments = if (isRaw) {
val defaultArgs = (1..classifier.typeParameters.size).map { ConeStarProjection }
if (forTypeParameterBounds) {
// 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
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
}
} else {
val typeParameters = runIf(!forTypeParameterBounds && !isForSupertypes) {
val classSymbol = session.symbolProvider.getClassLikeSymbolByFqName(classId) as? FirRegularClassSymbol
classSymbol?.fir?.typeParameters
} ?: emptyList()
typeArguments.indices.map { index ->
val argument = typeArguments[index]
val parameter = typeParameters.getOrNull(index)?.symbol?.fir
argument.toConeProjectionWithoutEnhancement(
session, javaTypeParameterStack, boundTypeParameter = parameter, isForSupertypes = isForSupertypes
)
}
}
lookupTag.constructClassType(mappedTypeArguments.toTypedArray(), isNullable = lowerBound != null, attributes)
}
is JavaTypeParameter -> {
val symbol = javaTypeParameterStack[classifier]
ConeTypeParameterTypeImpl(symbol.toLookupTag(), isNullable = lowerBound != null, attributes)
}
null -> {
val classId = ClassId.topLevel(FqName(this.classifierQualifiedName))
classId.constructClassLikeType(emptyArray(), isNullable = lowerBound != null, attributes)
}
else -> ConeKotlinErrorType(ConeSimpleDiagnostic("Unexpected classifier: $classifier", DiagnosticKind.Java))
}
}
// Returns true for covariant read-only container that has mutable pair with invariant parameter
// List<in A> does not make sense, but MutableList<in A> does
// Same for Map<K, in V>
// But both Iterable<in A>, MutableIterable<in A> don't make sense as they are covariant, so return false
private fun JavaClassifierType.argumentsMakeSenseOnlyForMutableContainer(
classId: ClassId,
session: FirSession,
): Boolean {
if (!JavaToKotlinClassMap.isReadOnly(classId.asSingleFqName().toUnsafe())) return false
val mutableClassId = classId.readOnlyToMutable() ?: return false
if (!typeArguments.lastOrNull().isSuperWildcard()) return false
val mutableLastParameterVariance =
(mutableClassId.toLookupTag().toSymbol(session)?.fir as? FirRegularClass)?.typeParameters?.lastOrNull()?.symbol?.fir?.variance
?: return false
return mutableLastParameterVariance != OUT_VARIANCE
}
private fun FirRegularClass.createRawArguments(
session: FirSession,
defaultArgs: List<ConeStarProjection>,
position: TypeComponentPosition
): List<ConeTypeProjection> = typeParameters.filterIsInstance<FirTypeParameter>().map { typeParameter ->
val erasedUpperBound = typeParameter.getErasedUpperBound(session) {
defaultType().withArguments(defaultArgs.toTypedArray())
}
computeRawProjection(session, typeParameter, position, erasedUpperBound)
}
private fun buildEnumCall(session: FirSession, classId: ClassId?, entryName: Name?) =
buildFunctionCall {
val calleeReference = if (classId != null && entryName != null) {
@@ -620,33 +224,6 @@ internal fun JavaValueParameter.toFirValueParameter(
}
}
private fun JavaType?.toConeProjectionWithoutEnhancement(
session: FirSession,
javaTypeParameterStack: JavaTypeParameterStack,
boundTypeParameter: FirTypeParameter?,
isForSupertypes: Boolean = false
): ConeTypeProjection {
return when (this) {
null -> ConeStarProjection
is JavaWildcardType -> {
val bound = this.bound
val argumentVariance = if (isExtends) OUT_VARIANCE else IN_VARIANCE
val parameterVariance = boundTypeParameter?.variance ?: INVARIANT
if (bound == null || parameterVariance != INVARIANT && parameterVariance != argumentVariance) {
ConeStarProjection
} else {
val boundType = bound.toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = isForSupertypes)
if (argumentVariance == OUT_VARIANCE) {
ConeKotlinTypeProjectionOut(boundType)
} else {
ConeKotlinTypeProjectionIn(boundType)
}
}
}
else -> toConeKotlinTypeWithoutEnhancement(session, javaTypeParameterStack, isForSupertypes = isForSupertypes)
}
}
internal fun JavaAnnotationArgument.toFirExpression(
session: FirSession, javaTypeParameterStack: JavaTypeParameterStack, expectedTypeRef: FirTypeRef?
): FirExpression {