FIR: Rework bare types support

^KT-48305 Fixed
This commit is contained in:
Denis.Zharkov
2021-08-26 18:13:55 +03:00
committed by TeamCityServer
parent 587146679d
commit 112af9b145
21 changed files with 253 additions and 93 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
import org.jetbrains.kotlin.fir.references.impl.FirSimpleNamedReference
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.dfa.unwrapSmartcastExpression
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.FirStubInferenceSession
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
@@ -589,40 +590,6 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
override fun <T> shouldRunCompletion(call: T): Boolean where T : FirStatement, T : FirResolvable = false
}
private fun ConeClassLikeType.inheritTypeArguments(
base: FirClassLikeDeclaration,
arguments: Array<out ConeTypeProjection>
): Array<out ConeTypeProjection>? {
val firClass = lookupTag.toSymbol(session)?.fir ?: return null
if (firClass !is FirTypeParameterRefsOwner || firClass.typeParameters.isEmpty()) return arrayOf()
return when (firClass) {
base -> arguments
is FirTypeAlias -> firClass.inheritTypeArguments(firClass.expandedTypeRef, base, arguments)
// TODO: if many supertypes, check consistency
is FirClass -> firClass.superTypeRefs.mapNotNull { firClass.inheritTypeArguments(it, base, arguments) }.firstOrNull()
else -> null
}
}
private fun FirTypeParameterRefsOwner.inheritTypeArguments(
typeRef: FirTypeRef,
base: FirClassLikeDeclaration,
arguments: Array<out ConeTypeProjection>
): Array<out ConeTypeProjection>? {
val type = typeRef.coneTypeSafe<ConeClassLikeType>() ?: return null
val indexMapping = typeParameters.map { parameter ->
// TODO: if many, check consistency of the result
type.typeArguments.indexOfFirst {
val argument = (it as? ConeKotlinType)?.lowerBoundIfFlexible()
argument is ConeTypeParameterType && argument.lookupTag.typeParameterSymbol == parameter.symbol
}
}
if (indexMapping.any { it == -1 }) return null
val typeArguments = type.inheritTypeArguments(base, arguments) ?: return null
return Array(typeParameters.size) { typeArguments[indexMapping[it]] }
}
private fun FirTypeRef.withTypeArgumentsForBareType(argument: FirExpression): FirTypeRef {
val type = coneTypeSafe<ConeClassLikeType>() ?: return this
if (type.typeArguments.isNotEmpty()) return this
@@ -630,43 +597,14 @@ open class FirExpressionsResolveTransformer(transformer: FirBodyResolveTransform
val firClass = type.lookupTag.toSymbol(session)?.fir ?: return this
if (firClass !is FirTypeParameterRefsOwner || firClass.typeParameters.isEmpty()) return this
val originalType = argument.typeRef.coneTypeSafe<ConeKotlinType>() ?: return this
val newType = computeRepresentativeTypeForBareType(type, originalType) ?: return buildErrorTypeRef {
val originalType = argument.unwrapSmartcastExpression().typeRef.coneTypeSafe<ConeKotlinType>() ?: return this
val newType = components.computeRepresentativeTypeForBareType(type, originalType) ?: return buildErrorTypeRef {
source = this@withTypeArgumentsForBareType.source
diagnostic = ConeNoTypeArgumentsOnRhsError(firClass.typeParameters.size, firClass.symbol)
}
return if (newType.typeArguments.isEmpty()) this else withReplacedConeType(newType)
}
private fun computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
@Suppress("NAME_SHADOWING")
val originalType = originalType.lowerBoundIfFlexible().fullyExpandedType(session)
if (originalType is ConeIntersectionType) {
val candidatesFromIntersectedTypes = originalType.intersectedTypes.mapNotNull { computeRepresentativeTypeForBareType(type, it) }
candidatesFromIntersectedTypes.firstOrNull { it.typeArguments.isNotEmpty() }?.let { return it }
return candidatesFromIntersectedTypes.firstOrNull()
}
if (originalType !is ConeClassLikeType) return type
val baseFirClass = originalType.lookupTag.toSymbol(session)?.fir ?: return type
val isSubtype = AbstractTypeChecker.isSubtypeOfClass(
session.typeContext.newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = true),
originalType.lookupTag,
type.lookupTag
)
val newArguments = if (isSubtype) {
// If actual type of declaration is more specific than bare type then we should just find
// corresponding supertype with proper arguments
with(session.typeContext) {
val superType = originalType.fastCorrespondingSupertypes(type.lookupTag)?.firstOrNull() as? ConeKotlinType?
superType?.typeArguments
}
} else {
type.inheritTypeArguments(baseFirClass, originalType.typeArguments)
} ?: return null
if (newArguments.isEmpty()) return type
return type.withArguments(newArguments)
}
override fun transformTypeOperatorCall(
typeOperatorCall: FirTypeOperatorCall,
data: ResolutionMode,
@@ -0,0 +1,179 @@
/*
* 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.resolve.transformers.body.resolve
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRef
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
originalType.lowerBoundIfFlexible().fullyExpandedType(session).let {
if (it !== originalType) return computeRepresentativeTypeForBareType(type, it)
}
if (originalType is ConeIntersectionType) {
val candidatesFromIntersectedTypes = originalType.intersectedTypes.mapNotNull { computeRepresentativeTypeForBareType(type, it) }
candidatesFromIntersectedTypes.firstOrNull { it.typeArguments.isNotEmpty() }?.let { return it }
return candidatesFromIntersectedTypes.firstOrNull()
}
val originalClassLookupTag = (originalType as? ConeClassLikeType)?.fullyExpandedType(session)?.lookupTag ?: return null
val castTypeAlias = type.lookupTag.toSymbol(session)?.fir as? FirTypeAlias
if (castTypeAlias != null && !canBeUsedAsBareType(castTypeAlias)) return null
val expandedCastType = type.fullyExpandedType(session)
val castClass = expandedCastType.lookupTag.toSymbol(session)?.fir as? FirRegularClass ?: return null
val superTypeWithParameters = with(session.typeContext) {
val correspondingSupertype = AbstractTypeChecker.findCorrespondingSupertypes(
newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false),
castClass.defaultType(), originalClassLookupTag,
).firstOrNull() as? ConeClassLikeType ?: return null
if (originalType.nullability.isNullable)
correspondingSupertype.withNullability(nullable = true) as ConeClassLikeType
else
correspondingSupertype
}
val substitution = mutableMapOf<FirTypeParameterRef, ConeTypeProjection>()
val typeParameters = castClass.typeParameters.mapTo(mutableSetOf()) { it.symbol.fir }
if (!doUnify(originalType, superTypeWithParameters, typeParameters, substitution)) return null
val newArguments = castClass.typeParameters.map { substitution[it.symbol.fir] ?: return@computeRepresentativeTypeForBareType null }
return expandedCastType.withArguments(newArguments.toTypedArray())
}
private fun canBeUsedAsBareType(firTypeAlias: FirTypeAlias): Boolean {
val typeAliasParameters = firTypeAlias.typeParameters.toSet()
val usedTypeParameters = mutableSetOf<FirTypeParameter>()
val expandedType = firTypeAlias.expandedConeType ?: return false
for (argument in expandedType.typeArguments) {
if (argument.kind == ProjectionKind.STAR) continue
if (argument.kind != ProjectionKind.INVARIANT) return false
val type = argument.type!!
val typeParameter = (type as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol?.fir ?: return false
if (typeParameter !in typeAliasParameters || typeParameter in usedTypeParameters) return false
usedTypeParameters.add(typeParameter)
}
return true
}
/**
* @return false does only mean that there were conflicted values for some type parameter. In all other cases, it returns true.
* "fail" result in the comments below means that we can't infer anything meaningful in that branch of unification.
* See more at org.jetbrains.kotlin.types.TypeUnifier.doUnify.
* NB: "Failed@ result of UnificationResultImpl is effectively unused in production.
*/
private fun BodyResolveComponents.doUnify(
originalTypeProjection: ConeTypeProjection,
typeWithParametersProjection: ConeTypeProjection,
targetTypeParameters: Set<FirTypeParameterRef>,
result: MutableMap<FirTypeParameterRef, ConeTypeProjection>,
): Boolean {
val originalType = originalTypeProjection.type
val typeWithParameters = typeWithParametersProjection.type
// in Foo ~ in X => Foo ~ X
if (originalTypeProjection.kind == typeWithParametersProjection.kind &&
originalTypeProjection.kind != ProjectionKind.INVARIANT && originalTypeProjection.kind != ProjectionKind.STAR) {
return doUnify(originalType!!, typeWithParameters!!, targetTypeParameters, result)
}
// Foo? ~ X? => Foo ~ X
if (originalType?.nullability?.isNullable == true && typeWithParameters?.nullability?.isNullable == true) {
return doUnify(
originalTypeProjection.removeQuestionMark(session.typeContext),
typeWithParametersProjection.removeQuestionMark(session.typeContext),
targetTypeParameters, result,
)
}
// in Foo ~ out X => fail
// in Foo ~ X => may be OK
if (originalTypeProjection.kind != typeWithParametersProjection.kind && typeWithParametersProjection.kind != ProjectionKind.INVARIANT) {
return true
}
if (typeWithParameters is ConeFlexibleType) {
return doUnify(
if (originalType?.nullability?.isNullable == true)
originalTypeProjection.removeQuestionMark(session.typeContext)
else
originalTypeProjection,
typeWithParametersProjection.replaceType(typeWithParameters.lowerBound),
targetTypeParameters, result,
)
}
// Foo ~ X? => fail
if (originalType?.nullability?.isNullable != true && typeWithParameters?.nullability?.isNullable == true) {
return true
}
// Foo ~ X => x |-> Foo
// * ~ X => x |-> *
val typeParameter = (typeWithParameters as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol?.fir
if (typeParameter != null && typeParameter in targetTypeParameters) {
if (typeParameter in result && result[typeParameter] != originalTypeProjection) return false
result[typeParameter] = originalTypeProjection
return true
}
// Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar
if (originalType?.nullability?.isNullable != typeWithParameters?.nullability?.isNullable) return true
if (originalTypeProjection.kind != typeWithParametersProjection.kind) return true
if ((originalType as? ConeLookupTagBasedType)?.lookupTag != (typeWithParameters as? ConeLookupTagBasedType)?.lookupTag) return true
if (originalType == null || typeWithParameters == null) return true
// Foo<A> ~ Foo<B, C>
if (originalType.typeArguments.size != typeWithParameters.typeArguments.size) {
return true
}
// Foo ~ Foo
if (originalType.typeArguments.isEmpty()) {
return true
}
// Foo<...> ~ Foo<...>
for ((originalTypeArgument, typeWithParametersArgument) in originalType.typeArguments.zip(typeWithParameters.typeArguments)) {
if (!doUnify(originalTypeArgument, typeWithParametersArgument, targetTypeParameters, result)) return false
}
return true
}
private fun ConeTypeProjection.removeQuestionMark(typeContext: ConeTypeContext): ConeTypeProjection {
val type = type
require(type != null && type.nullability.isNullable) {
"Expected nullable type, got $type"
}
return replaceType(type.withNullability(ConeNullability.NOT_NULL, typeContext))
}
private fun ConeTypeProjection.replaceType(newType: ConeKotlinType): ConeTypeProjection =
when (kind) {
ProjectionKind.INVARIANT -> newType
ProjectionKind.IN -> ConeKotlinTypeProjectionIn(newType)
ProjectionKind.OUT -> ConeKotlinTypeProjectionOut(newType)
ProjectionKind.STAR -> error("Should not be a star projection")
}
@@ -524,3 +524,8 @@ private class CapturedArguments(val capturedArguments: Array<ConeTypeProjection>
}
}
fun ConeKotlinType.isSubtypeOf(superType: ConeKotlinType, session: FirSession): Boolean =
AbstractTypeChecker.isSubtypeOf(
session.typeContext.newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false),
this, superType,
)