[FIR] Unify code of type unification, fix intersection types processing in FirCastDiagnosticsHelpers.kt
This commit is contained in:
committed by
TeamCityServer
parent
98cce8e05b
commit
5fda933c33
+58
-47
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
|||||||
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
|
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
|
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
|
||||||
@@ -145,14 +146,14 @@ fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: Ch
|
|||||||
// NOTE: this does not account for 'as Array<List<T>>'
|
// NOTE: this does not account for 'as Array<List<T>>'
|
||||||
if (subtype.allParameterReified()) return false
|
if (subtype.allParameterReified()) return false
|
||||||
|
|
||||||
val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype, context).first ?: return true
|
val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype, context)
|
||||||
|
|
||||||
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
// If the substitution failed, it means that the result is an impossible type, e.g. something like Out<in Foo>
|
||||||
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
// In this case, we can't guarantee anything, so the cast is considered to be erased
|
||||||
|
|
||||||
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
// If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead.
|
||||||
// If not, it's wrong to use it
|
// If not, it's wrong to use it
|
||||||
return !AbstractTypeChecker.isSubtypeOf(context.session.typeContext, staticallyKnownSubtype, subtype)
|
return !AbstractTypeChecker.isSubtypeOf(context.session.typeContext, staticallyKnownSubtype, subtype, stubTypesEqualToAnything = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ConeKotlinType.allParameterReified(): Boolean {
|
private fun ConeKotlinType.allParameterReified(): Boolean {
|
||||||
@@ -179,7 +180,7 @@ fun findStaticallyKnownSubtype(
|
|||||||
supertype: ConeKotlinType,
|
supertype: ConeKotlinType,
|
||||||
subtype: ConeKotlinType,
|
subtype: ConeKotlinType,
|
||||||
context: CheckerContext
|
context: CheckerContext
|
||||||
): Pair<ConeKotlinType?, Boolean> {
|
): ConeKotlinType {
|
||||||
assert(!supertype.isMarkedNullable) { "This method only makes sense for non-nullable types" }
|
assert(!supertype.isMarkedNullable) { "This method only makes sense for non-nullable types" }
|
||||||
|
|
||||||
val session = context.session
|
val session = context.session
|
||||||
@@ -194,60 +195,70 @@ fun findStaticallyKnownSubtype(
|
|||||||
// in this case it will be Collection<T>
|
// in this case it will be Collection<T>
|
||||||
val typeCheckerState = context.session.typeContext.newTypeCheckerState(
|
val typeCheckerState = context.session.typeContext.newTypeCheckerState(
|
||||||
errorTypesEqualToAnything = false,
|
errorTypesEqualToAnything = false,
|
||||||
stubTypesEqualToAnything = true
|
stubTypesEqualToAnything = false
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getFirstNotIntersectedType(type: ConeKotlinType): ConeKotlinType? {
|
val normalizedTypes = if (supertype is ConeIntersectionType) {
|
||||||
if (type is ConeIntersectionType) {
|
supertype.intersectedTypes
|
||||||
for (intersectionType in type.intersectedTypes) {
|
|
||||||
val result = getFirstNotIntersectedType(intersectionType)
|
|
||||||
if (result != null) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return type
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtaining not intersected type to get not null typeConstructor.
|
|
||||||
// Not sure if it's correct
|
|
||||||
val notIntersectedSupertype = getFirstNotIntersectedType(supertype) ?: supertype
|
|
||||||
val supertypeWithVariables =
|
|
||||||
findCorrespondingSupertypes(
|
|
||||||
typeCheckerState,
|
|
||||||
subtypeWithVariablesType,
|
|
||||||
notIntersectedSupertype.typeConstructor(typeContext)
|
|
||||||
).firstOrNull()
|
|
||||||
|
|
||||||
val variables = subtypeWithVariables.typeParameterSymbols
|
|
||||||
|
|
||||||
val substitution = if (supertypeWithVariables != null) {
|
|
||||||
// Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
|
|
||||||
val typeUnifier = TypeUnifier(session, variables)
|
|
||||||
val unificationResult = typeUnifier.unify(supertype, supertypeWithVariables as ConeKotlinTypeProjection)
|
|
||||||
unificationResult.substitution.toMutableMap()
|
|
||||||
} else {
|
} else {
|
||||||
mutableMapOf()
|
ArrayList<ConeKotlinType>(1).also { it.add(supertype) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// If some of the parameters are not determined by unification, it means that these parameters are lost,
|
val resultSubstitution = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
|
||||||
// let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
|
||||||
var allArgumentsInferred = true
|
for (normalizedType in normalizedTypes) {
|
||||||
for (variable in variables) {
|
val supertypeWithVariables =
|
||||||
val value = substitution[variable]
|
findCorrespondingSupertypes(
|
||||||
if (value == null) {
|
typeCheckerState,
|
||||||
substitution[variable] = context.session.builtinTypes.nullableAnyType.type
|
subtypeWithVariablesType,
|
||||||
allArgumentsInferred = false
|
normalizedType.typeConstructor(typeContext)
|
||||||
|
).firstOrNull()
|
||||||
|
|
||||||
|
val variables: List<FirTypeParameterSymbol> = subtypeWithVariables.typeParameterSymbols
|
||||||
|
|
||||||
|
val substitution = if (supertypeWithVariables != null) {
|
||||||
|
// Now, let's try to unify Collection<T> and Collection<Foo> solution is a map from T to Foo
|
||||||
|
val result = mutableMapOf<FirTypeParameterSymbol, ConeTypeProjection>()
|
||||||
|
if (context.session.doUnify(
|
||||||
|
supertype,
|
||||||
|
supertypeWithVariables as ConeKotlinTypeProjection,
|
||||||
|
variables.toSet(),
|
||||||
|
result
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
mutableMapOf()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mutableMapOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If some parameters are not determined by unification, it means that these parameters are lost,
|
||||||
|
// let's put ConeStubType instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*>
|
||||||
|
for (variable in variables) {
|
||||||
|
val value = substitution[variable]
|
||||||
|
val resultValue = if (value == null) {
|
||||||
|
if (!resultSubstitution.containsKey(variable)) {
|
||||||
|
context.session.builtinTypes.nullableAnyType.type
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else if (value is ConeStarProjection) {
|
||||||
|
ConeStubTypeForTypeVariableInSubtyping(ConeTypeVariable("", variable.toLookupTag()), ConeNullability.NULLABLE)
|
||||||
|
} else {
|
||||||
|
value.type
|
||||||
|
}
|
||||||
|
if (resultValue != null) {
|
||||||
|
resultSubstitution[variable] = resultValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have values for all type parameters of List
|
// At this point we have values for all type parameters of List
|
||||||
// Let's make a type by substituting them: List<T> -> List<Foo>
|
// Let's make a type by substituting them: List<T> -> List<Foo>
|
||||||
val substitutor = ConeSubstitutorByMap(substitution, session)
|
val substitutor = ConeSubstitutorByMap(resultSubstitution, session)
|
||||||
val substituted = substitutor.substituteOrSelf(subtypeWithVariablesType)
|
return substitutor.substituteOrSelf(subtypeWithVariablesType)
|
||||||
|
|
||||||
return Pair(substituted, allArgumentsInferred)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ConeKotlinType.isNonReifiedTypeParameter(): Boolean {
|
fun ConeKotlinType.isNonReifiedTypeParameter(): Boolean {
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.analysis.checkers
|
|
||||||
|
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
|
||||||
|
|
||||||
class TypeUnifier(private val session: FirSession, private val typeParameterSymbols: List<FirTypeParameterSymbol>) {
|
|
||||||
/**
|
|
||||||
* Finds a substitution S that turns {@code projectWithVariables} to {@code knownProjection}.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* known = List<String>
|
|
||||||
* withVariables = List<X>
|
|
||||||
* variables = {X}
|
|
||||||
*
|
|
||||||
* result = X -> String
|
|
||||||
*
|
|
||||||
* Only types accepted by {@code isVariable} are considered variables.
|
|
||||||
*/
|
|
||||||
fun unify(knownProjection: ConeKotlinTypeProjection, projectWithVariables: ConeKotlinTypeProjection): UnificationResult {
|
|
||||||
val result = UnificationResult()
|
|
||||||
doUnify(knownProjection, projectWithVariables, result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun doUnify(knownProjection: ConeTypeProjection, projectWithVariables: ConeTypeProjection, result: UnificationResult) {
|
|
||||||
val firstType = knownProjection.type
|
|
||||||
|
|
||||||
if (firstType is ConeIntersectionType) {
|
|
||||||
val intersectionResult = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
|
|
||||||
|
|
||||||
for (intersectedType in firstType.intersectedTypes) {
|
|
||||||
val localResult = UnificationResult()
|
|
||||||
doUnify(intersectedType, projectWithVariables, localResult)
|
|
||||||
|
|
||||||
for ((typeParameterSymbol, typeParameterType) in localResult.substitution) {
|
|
||||||
val existingTypeParameterType = intersectionResult[typeParameterSymbol]
|
|
||||||
if (existingTypeParameterType == null ||
|
|
||||||
AbstractTypeChecker.isSubtypeOf(session.typeContext, typeParameterType, existingTypeParameterType)
|
|
||||||
) {
|
|
||||||
intersectionResult[typeParameterSymbol] = typeParameterType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((typeParameterSymbol, typeParameterType) in intersectionResult) {
|
|
||||||
result.put(typeParameterSymbol, typeParameterType)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val known = firstType?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type
|
|
||||||
val withVariables = projectWithVariables.type?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type
|
|
||||||
|
|
||||||
// in Foo ~ in X => Foo ~ X
|
|
||||||
val knownProjectionKind = knownProjection.kind
|
|
||||||
val withVariablesProjectionKind = projectWithVariables.kind
|
|
||||||
if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != ProjectionKind.INVARIANT) {
|
|
||||||
doUnify(known, withVariables, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo? ~ X? => Foo ~ X
|
|
||||||
if (known.isMarkedNullable && withVariables.isMarkedNullable) {
|
|
||||||
doUnify(
|
|
||||||
known.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection(
|
|
||||||
knownProjectionKind
|
|
||||||
) as ConeKotlinTypeProjection,
|
|
||||||
withVariables.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection(
|
|
||||||
withVariablesProjectionKind
|
|
||||||
) as ConeKotlinTypeProjection,
|
|
||||||
result
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// in Foo ~ out X => fail
|
|
||||||
// in Foo ~ X => may be OK
|
|
||||||
if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != ProjectionKind.INVARIANT) {
|
|
||||||
result.fail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo ~ X? => fail
|
|
||||||
if (!known.isMarkedNullable && withVariables.isMarkedNullable) {
|
|
||||||
result.fail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo ~ X => x |-> Foo
|
|
||||||
// * ~ X => x |-> *
|
|
||||||
val maybeVariable = withVariables.toSymbol(session)
|
|
||||||
if (maybeVariable is FirTypeParameterSymbol && typeParameterSymbols.contains(maybeVariable)) {
|
|
||||||
result.put(maybeVariable, known)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar
|
|
||||||
val structuralMismatch = known.isMarkedNullable != withVariables.isMarkedNullable ||
|
|
||||||
knownProjectionKind != withVariablesProjectionKind ||
|
|
||||||
known.toSymbol(session) != maybeVariable
|
|
||||||
if (structuralMismatch) {
|
|
||||||
result.fail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo<A> ~ Foo<B, C>
|
|
||||||
if (known.typeArguments.size != withVariables.typeArguments.size) {
|
|
||||||
result.fail()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo ~ Foo
|
|
||||||
if (known.typeArguments.isEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo<...> ~ Foo<...>
|
|
||||||
val knownArguments = known.typeArguments
|
|
||||||
val withVariablesArguments = withVariables.typeArguments
|
|
||||||
for (index in knownArguments.indices) {
|
|
||||||
val knownArg = knownArguments[index]
|
|
||||||
val withVariablesArg = withVariablesArguments[index]
|
|
||||||
doUnify(knownArg, withVariablesArg, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnificationResult {
|
|
||||||
private var success: Boolean = true
|
|
||||||
private var failedVariables: MutableSet<FirTypeParameterSymbol> = mutableSetOf()
|
|
||||||
private val _substitution: MutableMap<FirTypeParameterSymbol, ConeKotlinType> = mutableMapOf()
|
|
||||||
val substitution: Map<FirTypeParameterSymbol, ConeKotlinType>
|
|
||||||
get() = _substitution
|
|
||||||
|
|
||||||
fun fail() {
|
|
||||||
success = false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun put(key: FirTypeParameterSymbol, value: ConeKotlinType) {
|
|
||||||
if (failedVariables.contains(key)) return
|
|
||||||
|
|
||||||
if (substitution.containsKey(key)) {
|
|
||||||
_substitution.remove(key)
|
|
||||||
failedVariables.add(key)
|
|
||||||
fail()
|
|
||||||
} else {
|
|
||||||
_substitution[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* 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.types
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||||
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
|
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
fun FirSession.doUnify(
|
||||||
|
originalTypeProjection: ConeTypeProjection,
|
||||||
|
typeWithParametersProjection: ConeTypeProjection,
|
||||||
|
targetTypeParameters: Set<FirTypeParameterSymbol>,
|
||||||
|
result: MutableMap<FirTypeParameterSymbol, ConeTypeProjection>,
|
||||||
|
): Boolean {
|
||||||
|
val originalType = originalTypeProjection.type?.lowerBoundIfFlexible()
|
||||||
|
val typeWithParameters = typeWithParametersProjection.type
|
||||||
|
|
||||||
|
if (originalType is ConeIntersectionType) {
|
||||||
|
val intersectionResult = mutableMapOf<FirTypeParameterSymbol, ConeTypeProjection>()
|
||||||
|
for (intersectedType in originalType.intersectedTypes) {
|
||||||
|
val localResult = mutableMapOf<FirTypeParameterSymbol, ConeTypeProjection>()
|
||||||
|
if (!doUnify(intersectedType, typeWithParametersProjection, targetTypeParameters, localResult)) return false
|
||||||
|
for ((typeParameter, typeProjection) in localResult) {
|
||||||
|
val existingTypeProjection = intersectionResult[typeParameter]
|
||||||
|
if (existingTypeProjection == null
|
||||||
|
|| (typeProjection is KotlinTypeMarker &&
|
||||||
|
existingTypeProjection is KotlinTypeMarker &&
|
||||||
|
AbstractTypeChecker.isSubtypeOf(typeContext, typeProjection, existingTypeProjection))
|
||||||
|
) {
|
||||||
|
intersectionResult[typeParameter] = typeProjection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((key, value) in intersectionResult) {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 == ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) {
|
||||||
|
return doUnify(
|
||||||
|
originalTypeProjection.removeQuestionMark(typeContext),
|
||||||
|
typeWithParametersProjection.removeQuestionMark(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(
|
||||||
|
originalTypeProjection,
|
||||||
|
typeWithParametersProjection.replaceType(typeWithParameters.lowerBound),
|
||||||
|
targetTypeParameters, result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo ~ X? => fail
|
||||||
|
if (originalType?.nullability != ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Foo ~ X => x |-> Foo
|
||||||
|
// * ~ X => x |-> *
|
||||||
|
val typeParameter = (typeWithParameters as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol
|
||||||
|
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")
|
||||||
|
}
|
||||||
+5
-126
@@ -8,15 +8,14 @@ package org.jetbrains.kotlin.fir.resolve.transformers.body.resolve
|
|||||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
|
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirTypeParameter
|
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.declarations.utils.expandedConeType
|
||||||
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
|
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
|
||||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
|
|
||||||
|
|
||||||
fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
|
fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? {
|
||||||
originalType.lowerBoundIfFlexible().fullyExpandedType(session).let {
|
originalType.lowerBoundIfFlexible().fullyExpandedType(session).let {
|
||||||
@@ -49,11 +48,11 @@ fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLi
|
|||||||
correspondingSupertype
|
correspondingSupertype
|
||||||
}
|
}
|
||||||
|
|
||||||
val substitution = mutableMapOf<FirTypeParameterRef, ConeTypeProjection>()
|
val substitution = mutableMapOf<FirTypeParameterSymbol, ConeTypeProjection>()
|
||||||
val typeParameters = castClass.typeParameters.mapTo(mutableSetOf()) { it.symbol.fir }
|
val typeParameters = castClass.typeParameters.mapTo(mutableSetOf()) { it.symbol }
|
||||||
if (!doUnify(originalType, superTypeWithParameters, typeParameters, substitution)) return null
|
if (!session.doUnify(originalType, superTypeWithParameters, typeParameters, substitution)) return null
|
||||||
|
|
||||||
val newArguments = castClass.typeParameters.map { substitution[it.symbol.fir] ?: return@computeRepresentativeTypeForBareType null }
|
val newArguments = castClass.typeParameters.map { substitution[it.symbol] ?: return@computeRepresentativeTypeForBareType null }
|
||||||
return expandedCastType.withArguments(newArguments.toTypedArray())
|
return expandedCastType.withArguments(newArguments.toTypedArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,123 +75,3 @@ private fun canBeUsedAsBareType(firTypeAlias: FirTypeAlias): Boolean {
|
|||||||
return true
|
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
|
|
||||||
|
|
||||||
if (originalType is ConeIntersectionType) {
|
|
||||||
val intersectionResult = mutableMapOf<FirTypeParameterRef, ConeTypeProjection>()
|
|
||||||
for (intersectedType in originalType.intersectedTypes) {
|
|
||||||
val localResult = mutableMapOf<FirTypeParameterRef, ConeTypeProjection>()
|
|
||||||
if (!doUnify(intersectedType, typeWithParametersProjection, targetTypeParameters, localResult)) return false
|
|
||||||
for ((typeParameter, typeProjection) in localResult) {
|
|
||||||
val existingTypeProjection = intersectionResult[typeParameter]
|
|
||||||
if (existingTypeProjection == null
|
|
||||||
|| (typeProjection is KotlinTypeMarker &&
|
|
||||||
existingTypeProjection is KotlinTypeMarker &&
|
|
||||||
AbstractTypeChecker.isSubtypeOf(session.typeContext, typeProjection, existingTypeProjection))
|
|
||||||
) {
|
|
||||||
intersectionResult[typeParameter] = typeProjection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ((key, value) in intersectionResult) {
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 == ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) {
|
|
||||||
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(
|
|
||||||
originalTypeProjection,
|
|
||||||
typeWithParametersProjection.replaceType(typeWithParameters.lowerBound),
|
|
||||||
targetTypeParameters, result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Foo ~ X? => fail
|
|
||||||
if (originalType?.nullability != ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
// KT-10444 Do not ignore smart (unchecked) casts to the same classifier
|
// KT-10444 Do not ignore smart (unchecked) casts to the same classifier
|
||||||
|
|
||||||
|
class Base<in T>
|
||||||
class Qwe<T : Any>(val a: T?) {
|
class Qwe<T : Any>(val a: T?) {
|
||||||
fun test1(obj: Any) {
|
fun test1(obj: Any) {
|
||||||
obj <!UNCHECKED_CAST!>as Qwe<T><!>
|
obj <!UNCHECKED_CAST!>as Qwe<T><!>
|
||||||
@@ -12,6 +13,10 @@ class Qwe<T : Any>(val a: T?) {
|
|||||||
check(obj.a)
|
check(obj.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test2(b: Base<*>) {
|
||||||
|
b <!UNCHECKED_CAST!>as Base<Any><!>
|
||||||
|
}
|
||||||
|
|
||||||
fun check(a: T?) {
|
fun check(a: T?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
// KT-10444 Do not ignore smart (unchecked) casts to the same classifier
|
// KT-10444 Do not ignore smart (unchecked) casts to the same classifier
|
||||||
|
|
||||||
|
class Base<in T>
|
||||||
class Qwe<T : Any>(val a: T?) {
|
class Qwe<T : Any>(val a: T?) {
|
||||||
fun test1(obj: Any) {
|
fun test1(obj: Any) {
|
||||||
obj <!UNCHECKED_CAST!>as Qwe<T><!>
|
obj <!UNCHECKED_CAST!>as Qwe<T><!>
|
||||||
@@ -12,6 +13,10 @@ class Qwe<T : Any>(val a: T?) {
|
|||||||
check(obj.a)
|
check(obj.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun test2(b: Base<*>) {
|
||||||
|
b <!UNCHECKED_CAST!>as Base<Any><!>
|
||||||
|
}
|
||||||
|
|
||||||
fun check(a: T?) {
|
fun check(a: T?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ public open class Bar</*0*/ T : Foo> {
|
|||||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class Base</*0*/ in T> {
|
||||||
|
public constructor Base</*0*/ in T>()
|
||||||
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
|
}
|
||||||
|
|
||||||
public open class Foo {
|
public open class Foo {
|
||||||
public constructor Foo()
|
public constructor Foo()
|
||||||
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
|
||||||
@@ -26,5 +33,7 @@ public final class Qwe</*0*/ T : kotlin.Any> {
|
|||||||
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
|
||||||
public final fun test1(/*0*/ obj: Qwe<*>): kotlin.Unit
|
public final fun test1(/*0*/ obj: Qwe<*>): kotlin.Unit
|
||||||
public final fun test1(/*0*/ obj: kotlin.Any): kotlin.Unit
|
public final fun test1(/*0*/ obj: kotlin.Any): kotlin.Unit
|
||||||
|
public final fun test2(/*0*/ b: Base<*>): kotlin.Unit
|
||||||
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user