Move common inference classes to :compiler:resolution.common

This commit is contained in:
Dmitriy Novozhilov
2020-08-26 11:08:30 +03:00
parent 068d21635e
commit 64766e125c
35 changed files with 78 additions and 164 deletions
@@ -0,0 +1,13 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api(project(":core:compiler.common"))
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
@@ -0,0 +1,449 @@
/*
* Copyright 2010-2020 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.resolve.calls
import org.jetbrains.kotlin.types.AbstractFlexibilityChecker.hasDifferentFlexibilityAtDepth
import org.jetbrains.kotlin.types.AbstractNullabilityChecker
import org.jetbrains.kotlin.types.AbstractNullabilityChecker.hasPathByNotMarkedNullableNodes
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.types.model.*
object NewCommonSuperTypeCalculator {
fun TypeSystemCommonSuperTypesContext.commonSuperType(types: List<KotlinTypeMarker>): KotlinTypeMarker {
val maxDepth = types.maxOfOrNull { it.typeDepth() } ?: 0
return commonSuperType(types, -maxDepth, true)
}
private fun TypeSystemCommonSuperTypesContext.commonSuperType(
types: List<KotlinTypeMarker>,
depth: Int,
isTopLevelType: Boolean = false
): KotlinTypeMarker {
if (types.isEmpty()) throw IllegalStateException("Empty collection for input")
types.singleOrNull()?.let { return it }
var thereIsFlexibleTypes = false
val lowers = types.map {
when (it) {
is SimpleTypeMarker -> {
if (it.isCapturedDynamic()) return it
it
}
is FlexibleTypeMarker -> {
if (it.isDynamic()) return it
// raw types are allowed here and will be transformed to FlexibleTypes
thereIsFlexibleTypes = true
it.lowerBound()
}
else -> error("sealed")
}
}
val contextStubTypesEqualToAnything = newBaseTypeCheckerContext(errorTypesEqualToAnything = false, stubTypesEqualToAnything = true)
val contextStubTypesNotEqual = newBaseTypeCheckerContext(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false)
val lowerSuperType = commonSuperTypeForSimpleTypes(lowers, depth, contextStubTypesEqualToAnything, contextStubTypesNotEqual)
if (!thereIsFlexibleTypes) return lowerSuperType
val upperSuperType = commonSuperTypeForSimpleTypes(
types.map { it.upperBoundIfFlexible() }, depth, contextStubTypesEqualToAnything, contextStubTypesNotEqual
)
if (!isTopLevelType) {
val nonStubTypes =
types.filter { !isStubRelatedType(it.lowerBoundIfFlexible()) && !isStubRelatedType(it.upperBoundIfFlexible()) }
val equalToEachOtherTypes = nonStubTypes.filter { potentialCommonSuperType ->
nonStubTypes.all {
AbstractTypeChecker.equalTypes(this, it, potentialCommonSuperType)
}
}
if (equalToEachOtherTypes.isNotEmpty()) {
// TODO: merge flexibilities of type arguments instead of select the first suitable type
return equalToEachOtherTypes.first()
}
}
return createFlexibleType(lowerSuperType, upperSuperType)
}
private fun TypeSystemCommonSuperTypesContext.commonSuperTypeForSimpleTypes(
types: List<SimpleTypeMarker>,
depth: Int,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext,
contextStubTypesNotEqual: AbstractTypeCheckerContext
): SimpleTypeMarker {
if (types.any { it.isError() }) {
return createErrorType("CST(${types.joinToString()}")
}
// i.e. result type also should be marked nullable
val notAllNotNull =
types.any { !isStubRelatedType(it) && !AbstractNullabilityChecker.isSubtypeOfAny(contextStubTypesEqualToAnything, it) }
val notNullTypes = if (notAllNotNull) types.map { it.withNullability(false) } else types
val commonSuperType = commonSuperTypeForNotNullTypes(notNullTypes, depth, contextStubTypesEqualToAnything, contextStubTypesNotEqual)
return if (notAllNotNull)
refineNullabilityForUndefinedNullability(types, commonSuperType) ?: commonSuperType.withNullability(true)
else
commonSuperType
}
private fun TypeSystemCommonSuperTypesContext.refineNullabilityForUndefinedNullability(
types: List<SimpleTypeMarker>,
commonSuperType: SimpleTypeMarker
): SimpleTypeMarker? {
if (!commonSuperType.canHaveUndefinedNullability()) return null
val actuallyNotNull =
types.all { hasPathByNotMarkedNullableNodes(it, commonSuperType.typeConstructor()) }
return if (actuallyNotNull) commonSuperType else null
}
// Makes representative sample, i.e. (A, B, A) -> (A, B)
private fun TypeSystemCommonSuperTypesContext.uniquify(
types: List<SimpleTypeMarker>,
contextStubTypesNotEqual: AbstractTypeCheckerContext
): List<SimpleTypeMarker> {
val uniqueTypes = arrayListOf<SimpleTypeMarker>()
for (type in types) {
val isNewUniqueType = uniqueTypes.all {
val equalsModuloFlexibility = AbstractTypeChecker.equalTypes(contextStubTypesNotEqual, it, type) &&
!it.typeConstructor().isIntegerLiteralTypeConstructor()
!equalsModuloFlexibility || hasDifferentFlexibilityAtDepth(listOf(it, type))
}
if (isNewUniqueType) {
uniqueTypes += type
}
}
return uniqueTypes
}
// This function leaves only supertypes, i.e. A0 is a strong supertype for A iff A != A0 && A <: A0
// Explanation: consider types (A : A0, B : B0, A0, B0), then CST(A, B, A0, B0) == CST(CST(A, A0), CST(B, B0)) == CST(A0, B0)
private fun TypeSystemCommonSuperTypesContext.filterSupertypes(
list: List<SimpleTypeMarker>,
contextStubTypesNotEqual: AbstractTypeCheckerContext
): List<SimpleTypeMarker> {
val supertypes = list.toMutableList()
val iterator = supertypes.iterator()
while (iterator.hasNext()) {
val potentialSubtype = iterator.next()
val isSubtype = supertypes.any { supertype ->
supertype !== potentialSubtype &&
AbstractTypeChecker.isSubtypeOf(contextStubTypesNotEqual, potentialSubtype, supertype) &&
!hasDifferentFlexibilityAtDepth(listOf(potentialSubtype, supertype))
}
if (isSubtype) iterator.remove()
}
return supertypes
}
/*
* Common Supertype calculator works with proper types and stub types (which is a replacement for non-proper types)
* Also, there are two invariant related to stub types:
* - resulting type should be only proper type
* - one of the input types is definitely proper type
* */
private fun TypeSystemCommonSuperTypesContext.commonSuperTypeForNotNullTypes(
types: List<SimpleTypeMarker>,
depth: Int,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext,
contextStubTypesNotEqual: AbstractTypeCheckerContext
): SimpleTypeMarker {
if (types.size == 1) return types.single()
val nonStubTypes = types.filter { !isStubRelatedType(it) }
if (nonStubTypes.size == 1) return nonStubTypes.single()
assert(nonStubTypes.isNotEmpty()) {
"There should be at least one non-stub type to compute common supertype but there are: $types"
}
val uniqueTypes = uniquify(nonStubTypes, contextStubTypesNotEqual)
if (uniqueTypes.size == 1) return uniqueTypes.single()
val explicitSupertypes = filterSupertypes(uniqueTypes, contextStubTypesNotEqual)
if (explicitSupertypes.size == 1) return explicitSupertypes.single()
findErrorTypeInSupertypes(explicitSupertypes, contextStubTypesEqualToAnything)?.let { return it }
findCommonIntegerLiteralTypesSuperType(explicitSupertypes)?.let { return it }
return findSuperTypeConstructorsAndIntersectResult(explicitSupertypes, depth, contextStubTypesEqualToAnything)
}
private fun TypeSystemCommonSuperTypesContext.isStubRelatedType(type: SimpleTypeMarker): Boolean {
return type.isStubType() || isCapturedStubType(type)
}
private fun TypeSystemCommonSuperTypesContext.isCapturedStubType(type: SimpleTypeMarker): Boolean {
val projectedType =
type.asCapturedType()?.typeConstructor()?.projection()?.takeUnless { it.isStarProjection() }?.getType() ?: return false
return projectedType.asSimpleType()?.isStubType() == true
}
private fun TypeSystemCommonSuperTypesContext.findErrorTypeInSupertypes(
types: List<SimpleTypeMarker>,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext
): SimpleTypeMarker? {
for (type in types) {
collectAllSupertypes(type, contextStubTypesEqualToAnything).firstOrNull { it.isError() }?.let { return it.toErrorType() }
}
return null
}
private fun TypeSystemCommonSuperTypesContext.findSuperTypeConstructorsAndIntersectResult(
types: List<SimpleTypeMarker>,
depth: Int,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext
): SimpleTypeMarker =
intersectTypes(
allCommonSuperTypeConstructors(types, contextStubTypesEqualToAnything)
.map { superTypeWithGivenConstructor(types, it, depth) }
)
/**
* Note that if there is captured type C, then no one else is not subtype of C => lowerType cannot help here
*/
private fun TypeSystemCommonSuperTypesContext.allCommonSuperTypeConstructors(
types: List<SimpleTypeMarker>,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext
): List<TypeConstructorMarker> {
val result = collectAllSupertypes(types.first(), contextStubTypesEqualToAnything)
// retain all super constructors of the first type that are present in the supertypes of all other types
for (type in types) {
if (type === types.first()) continue
result.retainAll(collectAllSupertypes(type, contextStubTypesEqualToAnything))
}
// remove all constructors that have subtype(s) with constructors from the resulting set - they are less precise
return result.filterNot { target ->
result.any { other ->
other != target && other.supertypes().any { it.typeConstructor() == target }
}
}
}
private fun TypeSystemCommonSuperTypesContext.collectAllSupertypes(
type: SimpleTypeMarker,
contextStubTypesEqualToAnything: AbstractTypeCheckerContext
) =
LinkedHashSet<TypeConstructorMarker>().apply {
contextStubTypesEqualToAnything.anySupertype(
type,
{ add(it.typeConstructor()); false },
{ AbstractTypeCheckerContext.SupertypesPolicy.LowerIfFlexible }
)
}
private fun TypeSystemCommonSuperTypesContext.superTypeWithGivenConstructor(
types: List<SimpleTypeMarker>,
constructor: TypeConstructorMarker,
depth: Int
): SimpleTypeMarker {
if (constructor.parametersCount() == 0) return createSimpleType(
constructor,
emptyList(),
nullable = false
)
val typeCheckerContext = newBaseTypeCheckerContext(errorTypesEqualToAnything = false, stubTypesEqualToAnything = true)
/**
* Sometimes one type can have several supertypes with given type constructor, suppose A <: List<Int> and A <: List<Double>.
* Also suppose that B <: List<String>.
* Note that common supertype for A and B is CS(List<Int>, List<String>) & CS(List<Double>, List<String>),
* but it is too complicated and we will return not so accurate type: CS(List<Int>, List<Double>, List<String>)
*/
val correspondingSuperTypes = types.flatMap {
with(AbstractTypeChecker) {
typeCheckerContext.findCorrespondingSupertypes(it, constructor)
}
}
val arguments = ArrayList<TypeArgumentMarker>(constructor.parametersCount())
for (index in 0 until constructor.parametersCount()) {
val parameter = constructor.getParameter(index)
var thereIsStar = false
val typeProjections = correspondingSuperTypes.mapNotNull {
val typeArgumentFromSupertype = it.getArgumentOrNull(index) ?: return@mapNotNull null
// We have to uncapture types with status FOR_SUBTYPING because such captured types are creating during
// `findCorrespondingSupertypes` call. Normally, we shouldn't create intermediate captured types here, it's needed only
// to check subtyping. It'll be fixed but for a while we do this uncapturing here
val typeArgument = uncaptureFromSubtyping(typeArgumentFromSupertype)
when {
typeArgument.isStarProjection() -> {
thereIsStar = true
null
}
typeArgument.getType().lowerBoundIfFlexible().isStubType() -> null
else -> typeArgument
}
}
// This is used for folding recursive types like Inv<Inv<*>> into Inv<*>
fun collapseRecursiveArgumentIfPossible(argument: TypeArgumentMarker): TypeArgumentMarker {
if (argument.isStarProjection()) return argument
val argumentType = argument.getType().asSimpleType()
val argumentConstructor = argumentType?.typeConstructor()
return if (argument.getVariance() == TypeVariance.OUT && argumentConstructor == constructor && argumentType.asArgumentList()[index].isStarProjection()) {
createStarProjection(parameter)
} else {
argument
}
}
val argument =
if (thereIsStar || typeProjections.isEmpty() || checkRecursion(types, typeProjections, parameter)) {
createStarProjection(parameter)
} else {
collapseRecursiveArgumentIfPossible(calculateArgument(parameter, typeProjections, depth))
}
arguments.add(argument)
}
return createSimpleType(constructor, arguments, nullable = false, isExtensionFunction = types.all { it.isExtensionFunction() })
}
private fun TypeSystemCommonSuperTypesContext.uncaptureFromSubtyping(typeArgument: TypeArgumentMarker): TypeArgumentMarker {
val capturedType = typeArgument.getType().asSimpleType()?.asCapturedType() ?: return typeArgument
if (capturedType.captureStatus() != CaptureStatus.FOR_SUBTYPING) return typeArgument
return capturedType.typeConstructor().projection()
}
private fun TypeSystemCommonSuperTypesContext.checkRecursion(
originalTypesForCst: List<SimpleTypeMarker>,
typeArgumentsForSuperConstructorParameter: List<TypeArgumentMarker>,
parameter: TypeParameterMarker,
): Boolean {
if (parameter.getVariance() == TypeVariance.IN)
return false // arguments for contravariant parameters are intersected, recursion should not be possible
val originalTypesSet = originalTypesForCst.toSet()
val typeArgumentsTypeSet = typeArgumentsForSuperConstructorParameter.map { it.getType().lowerBoundIfFlexible() }.toSet()
if (originalTypesSet.size != typeArgumentsTypeSet.size)
return false
// only needed in case of captured star projections in argument types
val originalTypeConstructorSet by lazy { typeConstructorsWithExpandedStarProjections(originalTypesSet).toSet() }
for (argumentType in typeArgumentsTypeSet) {
if (argumentType in originalTypesSet) continue
var starProjectionFound = false
for (supertype in supertypesIfCapturedStarProjection(argumentType).orEmpty()) {
if (supertype.lowerBoundIfFlexible().typeConstructor() !in originalTypeConstructorSet)
return false
else starProjectionFound = true
}
if (!starProjectionFound)
return false
}
return true
}
private fun TypeSystemCommonSuperTypesContext.typeConstructorsWithExpandedStarProjections(types: Set<SimpleTypeMarker>) = sequence {
for (type in types) {
if (isCapturedStarProjection(type)) {
for (supertype in supertypesIfCapturedStarProjection(type).orEmpty()) {
yield(supertype.lowerBoundIfFlexible().typeConstructor())
}
} else {
yield(type.typeConstructor())
}
}
}
private fun TypeSystemCommonSuperTypesContext.isCapturedStarProjection(type: SimpleTypeMarker): Boolean =
type.asCapturedType()?.typeConstructor()?.projection()?.isStarProjection() == true
private fun TypeSystemCommonSuperTypesContext.supertypesIfCapturedStarProjection(type: SimpleTypeMarker): Collection<KotlinTypeMarker>? {
val constructor = type.asCapturedType()?.typeConstructor() ?: return null
return if (constructor.projection().isStarProjection())
constructor.supertypes()
else null
}
// no star projections in arguments
private fun TypeSystemCommonSuperTypesContext.calculateArgument(
parameter: TypeParameterMarker,
arguments: List<TypeArgumentMarker>,
depth: Int
): TypeArgumentMarker {
if (depth > 0) {
return createStarProjection(parameter)
}
// Inv<A>, Inv<A> = Inv<A>
if (parameter.getVariance() == TypeVariance.INV && arguments.all { it.getVariance() == TypeVariance.INV }) {
val first = arguments.first()
if (arguments.all { it.getType() == first.getType() }) return first
}
val asOut: Boolean
if (parameter.getVariance() != TypeVariance.INV) {
asOut = parameter.getVariance() == TypeVariance.OUT
} else {
val thereIsOut = arguments.any { it.getVariance() == TypeVariance.OUT }
val thereIsIn = arguments.any { it.getVariance() == TypeVariance.IN }
if (thereIsOut) {
if (thereIsIn) {
// CS(Inv<out X>, Inv<in Y>) = Inv<*>
return createStarProjection(parameter)
} else {
asOut = true
}
} else {
asOut = !thereIsIn
}
}
// CS(Out<X>, Out<Y>) = Out<CS(X, Y)>
// CS(In<X>, In<Y>) = In<X & Y>
// CS(Inv<X>, Inv<Y>) = Inv<out CS(X, Y)>)
if (asOut) {
val argumentTypes = arguments.map { it.getType() }
val parameterIsNotInv = parameter.getVariance() != TypeVariance.INV
if (parameterIsNotInv) {
return commonSuperType(argumentTypes, depth + 1).asTypeArgument()
}
val equalToEachOtherType = arguments.firstOrNull { potentialSuperType ->
arguments.all { AbstractTypeChecker.equalTypes(this, it.getType(), potentialSuperType.getType()) }
}
return if (equalToEachOtherType == null) {
createTypeArgument(commonSuperType(argumentTypes, depth + 1), TypeVariance.OUT)
} else {
val thereIsNotInv = arguments.any { it.getVariance() != TypeVariance.INV }
createTypeArgument(equalToEachOtherType.getType(), if (thereIsNotInv) TypeVariance.OUT else TypeVariance.INV)
}
} else {
val type = intersectTypes(arguments.map { it.getType() })
return if (parameter.getVariance() != TypeVariance.INV) type.asTypeArgument() else createTypeArgument(
type,
TypeVariance.IN
)
}
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2020 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.resolve.calls.components
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.types.model.*
interface PostponedArgumentsAnalyzerContext : TypeSystemInferenceExtensionContext {
fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, StubTypeMarker>): TypeSubstitutorMarker
fun buildNotFixedVariablesToStubTypesSubstitutor(): TypeSubstitutorMarker
fun bindingStubsForPostponedVariables(): Map<TypeVariableMarker, StubTypeMarker>
// type can be proper if it not contains not fixed type variables
fun canBeProper(type: KotlinTypeMarker): Boolean
fun hasUpperOrEqualUnitConstraint(type: KotlinTypeMarker): Boolean
// mutable operations
fun addOtherSystem(otherSystem: ConstraintStorage)
fun getBuilder(): ConstraintSystemBuilder
}
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeSubstitutorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
interface ConstraintSystemOperation {
val hasContradiction: Boolean
fun registerVariable(variable: TypeVariableMarker)
fun markPostponedVariable(variable: TypeVariableMarker)
fun unmarkPostponedVariable(variable: TypeVariableMarker)
fun removePostponedVariables()
fun addSubtypeConstraint(lowerType: KotlinTypeMarker, upperType: KotlinTypeMarker, position: ConstraintPosition)
fun addEqualityConstraint(a: KotlinTypeMarker, b: KotlinTypeMarker, position: ConstraintPosition)
fun isProperType(type: KotlinTypeMarker): Boolean
fun isTypeVariable(type: KotlinTypeMarker): Boolean
fun isPostponedTypeVariable(typeVariable: TypeVariableMarker): Boolean
fun getProperSuperTypeConstructors(type: KotlinTypeMarker): List<TypeConstructorMarker>
fun addOtherSystem(otherSystem: ConstraintStorage)
}
interface ConstraintSystemBuilder : ConstraintSystemOperation {
// if runOperations return true, then this operation will be applied, and function return true
fun runTransaction(runOperations: ConstraintSystemOperation.() -> Boolean): Boolean
fun buildCurrentSubstitutor(): TypeSubstitutorMarker
fun currentStorage(): ConstraintStorage
}
fun ConstraintSystemBuilder.addSubtypeConstraintIfCompatible(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
position: ConstraintPosition
): Boolean = addConstraintIfCompatible(lowerType, upperType, position, ConstraintKind.LOWER)
fun ConstraintSystemBuilder.addEqualityConstraintIfCompatible(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
position: ConstraintPosition
): Boolean = addConstraintIfCompatible(lowerType, upperType, position, ConstraintKind.EQUALITY)
private fun ConstraintSystemBuilder.addConstraintIfCompatible(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
position: ConstraintPosition,
kind: ConstraintKind
): Boolean = runTransaction {
if (!hasContradiction) {
when (kind) {
ConstraintKind.LOWER -> addSubtypeConstraint(lowerType, upperType, position)
ConstraintKind.UPPER -> addSubtypeConstraint(upperType, lowerType, position)
ConstraintKind.EQUALITY -> addEqualityConstraint(lowerType, upperType, position)
}
}
!hasContradiction
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.types.model.*
fun ConstraintStorage.buildCurrentSubstitutor(
context: TypeSystemInferenceExtensionContext,
additionalBindings: Map<TypeConstructorMarker, StubTypeMarker>
): TypeSubstitutorMarker {
return context.typeSubstitutorByTypeConstructor(fixedTypeVariables.entries.associate { it.key to it.value } + additionalBindings)
}
fun ConstraintStorage.buildAbstractResultingSubstitutor(
context: TypeSystemInferenceExtensionContext,
transformTypeVariablesToErrorTypes: Boolean = true
): TypeSubstitutorMarker = with(context) {
if (allTypeVariables.isEmpty()) return createEmptySubstitutor()
val currentSubstitutorMap = fixedTypeVariables.entries.associate {
it.key to it.value
}
val uninferredSubstitutorMap = if (transformTypeVariablesToErrorTypes) {
notFixedTypeVariables.entries.associate { (freshTypeConstructor, typeVariable) ->
freshTypeConstructor to context.createErrorTypeWithCustomConstructor(
"Uninferred type",
(typeVariable.typeVariable).freshTypeConstructor()
)
}
} else {
notFixedTypeVariables.entries.associate { (freshTypeConstructor, typeVariable) ->
freshTypeConstructor to typeVariable.typeVariable.defaultType(this)
}
}
return context.typeSubstitutorByTypeConstructor(currentSubstitutorMap + uninferredSubstitutorMap)
}
fun ConstraintStorage.buildNotFixedVariablesToNonSubtypableTypesSubstitutor(
context: TypeSystemInferenceExtensionContext
): TypeSubstitutorMarker {
return context.typeSubstitutorByTypeConstructor(
notFixedTypeVariables.mapValues { context.createStubType(it.value.typeVariable) }
)
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference
import org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzerContext
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionContext
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintSystemError
interface NewConstraintSystem {
val hasContradiction: Boolean
val errors: List<ConstraintSystemError>
fun getBuilder(): ConstraintSystemBuilder
// after this method we shouldn't mutate system via ConstraintSystemBuilder
fun asReadOnlyStorage(): ConstraintStorage
fun asConstraintSystemCompleterContext(): ConstraintSystemCompletionContext
fun asPostponedArgumentsAnalyzerContext(): PostponedArgumentsAnalyzerContext
}
@@ -0,0 +1,362 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.types.AbstractNullabilityChecker
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.types.model.*
abstract class AbstractTypeCheckerContextForConstraintSystem : AbstractTypeCheckerContext(), TypeSystemInferenceExtensionContext {
override val KotlinTypeMarker.isAllowedTypeVariable: Boolean
get() = false
override val isErrorTypeEqualsToAnything: Boolean
get() = true
override val isStubTypeEqualsToAnything: Boolean
get() = true
abstract fun isMyTypeVariable(type: SimpleTypeMarker): Boolean
// super and sub type isSingleClassifierType
abstract fun addUpperConstraint(typeVariable: TypeConstructorMarker, superType: KotlinTypeMarker)
abstract fun addLowerConstraint(
typeVariable: TypeConstructorMarker,
subType: KotlinTypeMarker,
isFromNullabilityConstraint: Boolean = false
)
override fun getLowerCapturedTypePolicy(subType: SimpleTypeMarker, superType: CapturedTypeMarker): LowerCapturedTypePolicy {
return when {
isMyTypeVariable(subType) -> {
val projection = superType.typeConstructorProjection()
val type = projection.getType().asSimpleType()
if (projection.getVariance() == TypeVariance.IN && type != null && isMyTypeVariable(type)) {
LowerCapturedTypePolicy.CHECK_ONLY_LOWER
} else {
LowerCapturedTypePolicy.SKIP_LOWER
}
}
subType.contains { it.anyBound(this::isMyTypeVariable) } -> LowerCapturedTypePolicy.CHECK_ONLY_LOWER
else -> LowerCapturedTypePolicy.CHECK_SUBTYPE_AND_LOWER
}
}
/**
* todo: possible we should override this method, because otherwise OR in subtyping transformed to AND in constraint system
* Now we cannot do this, because sometimes we have proper intersection type as lower type and if we first supertype,
* then we can get wrong result.
* override val sameConstructorPolicy get() = SeveralSupertypesWithSameConstructorPolicy.TAKE_FIRST_FOR_SUBTYPING
*/
final override fun addSubtypeConstraint(
subType: KotlinTypeMarker,
superType: KotlinTypeMarker,
isFromNullabilityConstraint: Boolean
): Boolean? {
val hasNoInfer = subType.isTypeVariableWithNoInfer() || superType.isTypeVariableWithNoInfer()
if (hasNoInfer) return true
val hasExact = subType.isTypeVariableWithExact() || superType.isTypeVariableWithExact()
// we should strip annotation's because we have incorporation operation and they should be not affected
val mySubType =
if (hasExact) extractTypeForProjectedType(subType, out = true) ?: subType.removeExactAnnotation() else subType
val mySuperType =
if (hasExact) extractTypeForProjectedType(superType, out = false) ?: superType.removeExactAnnotation() else superType
val result = internalAddSubtypeConstraint(mySubType, mySuperType, isFromNullabilityConstraint)
if (!hasExact) return result
val result2 = internalAddSubtypeConstraint(mySuperType, mySubType, isFromNullabilityConstraint)
if (result == null && result2 == null) return null
return (result ?: true) && (result2 ?: true)
}
private fun extractTypeForProjectedType(type: KotlinTypeMarker, out: Boolean): KotlinTypeMarker? {
val typeMarker = type.asSimpleType()?.asCapturedType() ?: return null
val projection = typeMarker.typeConstructorProjection()
if (projection.isStarProjection()) return null
return when (projection.getVariance()) {
TypeVariance.IN -> if (!out) typeMarker.lowerType() ?: projection.getType() else null
TypeVariance.OUT -> if (out) projection.getType() else null
TypeVariance.INV -> null
}
}
private fun KotlinTypeMarker.isTypeVariableWithExact() =
hasExactAnnotation() && anyBound(this@AbstractTypeCheckerContextForConstraintSystem::isMyTypeVariable)
private fun KotlinTypeMarker.isTypeVariableWithNoInfer() =
hasNoInferAnnotation() && anyBound(this@AbstractTypeCheckerContextForConstraintSystem::isMyTypeVariable)
private fun internalAddSubtypeConstraint(
subType: KotlinTypeMarker,
superType: KotlinTypeMarker,
isFromNullabilityConstraint: Boolean
): Boolean? {
assertInputTypes(subType, superType)
var answer: Boolean? = null
if (superType.anyBound(this::isMyTypeVariable)) {
answer = simplifyLowerConstraint(superType, subType, isFromNullabilityConstraint)
}
if (subType.anyBound(this::isMyTypeVariable)) {
return simplifyUpperConstraint(subType, superType) && (answer ?: true)
} else {
extractTypeVariableForSubtype(subType, superType)?.let {
return simplifyUpperConstraint(it, superType) && (answer ?: true)
}
return simplifyConstraintForPossibleIntersectionSubType(subType, superType) ?: answer
}
}
// extract type variable only from type like Captured(out T)
private fun extractTypeVariableForSubtype(subType: KotlinTypeMarker, superType: KotlinTypeMarker): KotlinTypeMarker? {
val typeMarker = subType.asSimpleType()?.asCapturedType() ?: return null
val projection = typeMarker.typeConstructorProjection()
if (projection.isStarProjection()) return null
if (projection.getVariance() == TypeVariance.IN) {
val type = projection.getType().asSimpleType() ?: return null
if (isMyTypeVariable(type)) {
simplifyLowerConstraint(type, superType)
if (isMyTypeVariable(superType.asSimpleType() ?: return null)) {
addLowerConstraint(superType.typeConstructor(), nullableAnyType())
}
}
return null
}
return if (projection.getVariance() == TypeVariance.OUT) {
val type = projection.getType()
when {
type is SimpleTypeMarker && isMyTypeVariable(type) -> type.asSimpleType()
type is FlexibleTypeMarker && isMyTypeVariable(type.lowerBound()) -> type.asFlexibleType()?.lowerBound()
else -> null
}
} else null
}
/**
* Foo <: T -- leave as is
*
* T?
*
* Foo <: T? -- Foo & Any <: T -- Foo!! <: T
* Foo? <: T? -- Foo? & Any <: T -- Foo!! <: T
* (Foo..Bar) <: T? -- (Foo..Bar) & Any <: T -- (Foo..Bar)!! <: T
*
* T!
*
* Foo <: T! --
* assert T! == (T..T?)
* Foo <: T?
* Foo <: T (optional constraint, needs to preserve nullability)
* =>
* Foo & Any <: T
* Foo <: T
* =>
* (Foo & Any .. Foo) <: T -- (Foo!! .. Foo) <: T
*
* => Foo <: T! -- (Foo!! .. Foo) <: T
*
* Foo? <: T! -- Foo? <: T
*
*
* (Foo..Bar) <: T! --
* assert T! == (T..T?)
* (Foo..Bar) <: (T..T?)
* =>
* Foo <: T?
* Bar <: T (optional constraint, needs to preserve nullability)
* =>
* (Foo & Any .. Bar) <: T -- (Foo!! .. Bar) <: T
*
* => (Foo..Bar) <: T! -- (Foo!! .. Bar) <: T
*/
private fun simplifyLowerConstraint(
typeVariable: KotlinTypeMarker,
subType: KotlinTypeMarker,
isFromNullabilityConstraint: Boolean = false
): Boolean {
val lowerConstraint = when (typeVariable) {
is SimpleTypeMarker ->
/*
* Foo <: T -- Foo <: T
* Foo <: T? (T is contained in invariant or contravariant positions of a return type) -- Foo <: T
* Example:
* fun <T> foo(x: T?): Inv<T> {}
* fun <K> main(z: K) { val x = foo(z) }
* Foo <: T? (T isn't contained there) -- Foo!! <: T
* Example:
* fun <T> foo(x: T?) {}
* fun <K> main(z: K) { foo(z) }
*/
if (typeVariable.isMarkedNullable()) {
val typeVariableTypeConstructor = typeVariable.typeConstructor()
val subTypeConstructor = subType.typeConstructor()
if (
!subTypeConstructor.isTypeVariable() &&
typeVariableTypeConstructor.isTypeVariable() &&
(typeVariableTypeConstructor as TypeVariableTypeConstructorMarker).isContainedInInvariantOrContravariantPositions()
) {
if (subType.isCapturedType()) {
(subType as CapturedTypeMarker).withNotNullProjection()
} else {
subType.withNullability(false)
}
} else {
subType.makeDefinitelyNotNullOrNotNull()
}
} else subType
is FlexibleTypeMarker -> {
assertFlexibleTypeVariable(typeVariable)
when (subType) {
is SimpleTypeMarker ->
// Foo <: T! -- (Foo!! .. Foo) <: T
if (subType.isMarkedNullable()) {
subType // prefer nullable type to flexible one: `Foo? <: (T..T?)` => lowerConstraint = `Foo?`
} else {
createFlexibleType(subType, subType.withNullability(true))
}
is FlexibleTypeMarker ->
// (Foo..Bar) <: T! -- (Foo!! .. Bar) <: T
createFlexibleType(subType.lowerBound().makeSimpleTypeDefinitelyNotNullOrNotNull(), subType.upperBound())
else -> error("sealed")
}
}
else -> error("sealed")
}
addLowerConstraint(typeVariable.typeConstructor(), lowerConstraint, isFromNullabilityConstraint)
return true
}
private fun assertFlexibleTypeVariable(typeVariable: FlexibleTypeMarker) {
assert(typeVariable.lowerBound().typeConstructor() == typeVariable.upperBound().typeConstructor()) {
"Flexible type variable ($typeVariable) should have bounds with the same type constructor, i.e. (T..T?)"
}
}
/**
* T! <: Foo <=> T <: Foo..Foo?
* T? <: Foo <=> T <: Foo && Nothing? <: Foo
* T <: Foo -- leave as is
*/
private fun simplifyUpperConstraint(typeVariable: KotlinTypeMarker, superType: KotlinTypeMarker): Boolean {
val typeVariableLowerBound = typeVariable.lowerBoundIfFlexible()
val simplifiedSuperType = if (typeVariableLowerBound.isDefinitelyNotNullType()) {
superType.withNullability(true)
} else if (typeVariable.isFlexible() && superType is SimpleTypeMarker) {
createFlexibleType(superType, superType.withNullability(true))
} else superType
addUpperConstraint(typeVariableLowerBound.typeConstructor(), simplifiedSuperType)
if (typeVariableLowerBound.isMarkedNullable()) {
// here is important that superType is singleClassifierType
return simplifiedSuperType.anyBound(this::isMyTypeVariable) ||
isSubtypeOfByTypeChecker(nullableNothingType(), simplifiedSuperType)
}
return true
}
private fun simplifyConstraintForPossibleIntersectionSubType(subType: KotlinTypeMarker, superType: KotlinTypeMarker): Boolean? {
@Suppress("NAME_SHADOWING")
val subType = subType.lowerBoundIfFlexible()
if (!subType.typeConstructor().isIntersection()) return null
assert(!subType.isMarkedNullable()) { "Intersection type should not be marked nullable!: $subType" }
// TODO: may be we lose flexibility here
val subIntersectionTypes = (subType.typeConstructor().supertypes()).map { it.lowerBoundIfFlexible() }
val typeVariables = subIntersectionTypes.filter(this::isMyTypeVariable).takeIf { it.isNotEmpty() } ?: return null
val notTypeVariables = subIntersectionTypes.filterNot(this::isMyTypeVariable)
// todo: may be we can do better then that.
if (notTypeVariables.isNotEmpty() &&
AbstractTypeChecker.isSubtypeOf(
this as TypeCheckerProviderContext,
intersectTypes(notTypeVariables),
superType
)
) {
return true
}
// Consider the following example:
// fun <T> id(x: T): T = x
// fun <S> id2(x: S?, y: S): S = y
//
// fun checkLeftAssoc(a: Int?) : Int {
// return id2(id(a), 3)
// }
//
// fun box() : String {
// return "OK"
// }
//
// here we try to add constraint {Any & T} <: S from `id(a)`
// Previously we thought that if `Any` isn't a subtype of S => T <: S, which is wrong, now we use weaker upper constraint
// TODO: rethink, maybe we should take nullability into account somewhere else
if (notTypeVariables.any { AbstractNullabilityChecker.isSubtypeOfAny(this as TypeCheckerProviderContext, it) }) {
return typeVariables.all { simplifyUpperConstraint(it, superType.withNullability(true)) }
}
return typeVariables.all { simplifyUpperConstraint(it, superType) }
}
private fun isSubtypeOfByTypeChecker(subType: KotlinTypeMarker, superType: KotlinTypeMarker) =
AbstractTypeChecker.isSubtypeOf(this as AbstractTypeCheckerContext, subType, superType)
private fun assertInputTypes(subType: KotlinTypeMarker, superType: KotlinTypeMarker) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
fun correctSubType(subType: SimpleTypeMarker) =
subType.isSingleClassifierType() || subType.typeConstructor().isIntersection() || isMyTypeVariable(subType) || subType.isError() || subType.isIntegerLiteralType()
fun correctSuperType(superType: SimpleTypeMarker) =
superType.isSingleClassifierType() || superType.typeConstructor().isIntersection() || isMyTypeVariable(superType) || superType.isError() || superType.isIntegerLiteralType()
assert(subType.bothBounds(::correctSubType)) {
"Not singleClassifierType and not intersection subType: $subType"
}
assert(superType.bothBounds(::correctSuperType)) {
"Not singleClassifierType superType: $superType"
}
}
private inline fun KotlinTypeMarker.bothBounds(f: (SimpleTypeMarker) -> Boolean) = when (this) {
is SimpleTypeMarker -> f(this)
is FlexibleTypeMarker -> f(lowerBound()) && f(upperBound())
else -> error("sealed")
}
private inline fun KotlinTypeMarker.anyBound(f: (SimpleTypeMarker) -> Boolean) = when (this) {
is SimpleTypeMarker -> f(this)
is FlexibleTypeMarker -> f(lowerBound()) || f(upperBound())
else -> error("sealed")
}
}
@@ -0,0 +1,335 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.types.AbstractTypeApproximator
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
// todo problem: intersection types in constrains: A <: Number, B <: Inv<A & Any> =>? B <: Inv<out Number & Any>
class ConstraintIncorporator(
val typeApproximator: AbstractTypeApproximator,
val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle,
val utilContext: ConstraintSystemUtilContext
) {
interface Context : TypeSystemInferenceExtensionContext {
val allTypeVariablesWithConstraints: Collection<VariableWithConstraints>
// if such type variable is fixed then it is error
fun getTypeVariable(typeConstructor: TypeConstructorMarker): TypeVariableMarker?
fun getConstraintsForVariable(typeVariable: TypeVariableMarker): Collection<Constraint>
fun addNewIncorporatedConstraint(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
shouldTryUseDifferentFlexibilityForUpperType: Boolean,
isFromNullabilityConstraint: Boolean = false,
isFromDeclaredUpperBound: Boolean = false
)
fun addNewIncorporatedConstraint(typeVariable: TypeVariableMarker, type: KotlinTypeMarker, constraintContext: ConstraintContext)
}
fun incorporateIntoOtherConstraints(c: Context, typeVariable: TypeVariableMarker, constraint: Constraint) {
// we shouldn't incorporate recursive constraint -- It is too dangerous
if (c.areThereRecursiveConstraints(typeVariable, constraint)) return
c.insideOtherConstraint(typeVariable, constraint)
}
// \alpha is typeVariable, \beta -- other type variable registered in ConstraintStorage
fun incorporate(c: Context, typeVariable: TypeVariableMarker, constraint: Constraint) {
// we shouldn't incorporate recursive constraint -- It is too dangerous
if (c.areThereRecursiveConstraints(typeVariable, constraint)) return
c.directWithVariable(typeVariable, constraint)
c.otherInsideMyConstraint(typeVariable, constraint)
c.insideOtherConstraint(typeVariable, constraint)
}
private fun Context.areThereRecursiveConstraints(typeVariable: TypeVariableMarker, constraint: Constraint) =
constraint.type.contains { it.typeConstructor() == typeVariable.freshTypeConstructor() }
// A <:(=) \alpha <:(=) B => A <: B
private fun Context.directWithVariable(
typeVariable: TypeVariableMarker,
constraint: Constraint
) {
val shouldBeTypeVariableFlexible = with(utilContext) { typeVariable.shouldBeFlexible() }
// \alpha <: constraint.type
if (constraint.kind != ConstraintKind.LOWER) {
getConstraintsForVariable(typeVariable).forEach {
if (it.kind != ConstraintKind.UPPER) {
addNewIncorporatedConstraint(it.type, constraint.type, shouldBeTypeVariableFlexible, it.isNullabilityConstraint)
}
}
}
// constraint.type <: \alpha
if (constraint.kind != ConstraintKind.UPPER) {
getConstraintsForVariable(typeVariable).forEach {
if (it.kind != ConstraintKind.LOWER) {
val isFromDeclaredUpperBound =
it.position.from is DeclaredUpperBoundConstraintPosition<*> && !it.type.typeConstructor().isTypeVariable()
addNewIncorporatedConstraint(
constraint.type,
it.type,
shouldBeTypeVariableFlexible,
isFromDeclaredUpperBound = isFromDeclaredUpperBound
)
}
}
}
}
// \alpha <: Inv<\beta>, \beta <: Number => \alpha <: Inv<out Number>
private fun Context.otherInsideMyConstraint(
typeVariable: TypeVariableMarker,
constraint: Constraint
) {
val otherInMyConstraint = SmartSet.create<TypeVariableMarker>()
constraint.type.contains {
otherInMyConstraint.addIfNotNull(this.getTypeVariable(it.typeConstructor()))
false
}
for (otherTypeVariable in otherInMyConstraint) {
// to avoid ConcurrentModificationException
val otherConstraints = SmartList(this.getConstraintsForVariable(otherTypeVariable))
for (otherConstraint in otherConstraints) {
generateNewConstraint(typeVariable, constraint, otherTypeVariable, otherConstraint)
}
}
}
// \alpha <: Number, \beta <: Inv<\alpha> => \beta <: Inv<out Number>
private fun Context.insideOtherConstraint(
typeVariable: TypeVariableMarker,
constraint: Constraint
) {
val freshTypeConstructor = typeVariable.freshTypeConstructor()
for (typeVariableWithConstraint in this@insideOtherConstraint.allTypeVariablesWithConstraints) {
val constraintsWhichConstraintMyVariable = typeVariableWithConstraint.constraints.filter {
it.type.contains { it.typeConstructor() == freshTypeConstructor }
}
constraintsWhichConstraintMyVariable.forEach {
generateNewConstraint(typeVariableWithConstraint.typeVariable, it, typeVariable, constraint)
}
}
}
private fun Context.approximateIfNeededAndAddNewConstraint(
baseConstraint: Constraint,
type: KotlinTypeMarker,
targetVariable: TypeVariableMarker,
otherVariable: TypeVariableMarker,
otherConstraint: Constraint,
needApproximation: Boolean = true
) {
val typeWithSubstitution = baseConstraint.type.substitute(this, otherVariable, type)
val prepareType = { toSuper: Boolean ->
if (needApproximation) approximateCapturedTypes(typeWithSubstitution, toSuper) else typeWithSubstitution
}
if (baseConstraint.kind != ConstraintKind.LOWER) {
addNewConstraint(targetVariable, baseConstraint, otherVariable, otherConstraint, prepareType(true), isSubtype = false)
}
if (baseConstraint.kind != ConstraintKind.UPPER) {
addNewConstraint(targetVariable, baseConstraint, otherVariable, otherConstraint, prepareType(false), isSubtype = true)
}
}
private fun Context.generateNewConstraint(
targetVariable: TypeVariableMarker,
baseConstraint: Constraint,
otherVariable: TypeVariableMarker,
otherConstraint: Constraint
) {
val isBaseGenericType = baseConstraint.type.argumentsCount() != 0
val isOtherCapturedType = otherConstraint.type.isCapturedType()
val (type, needApproximation) = when (otherConstraint.kind) {
ConstraintKind.EQUALITY -> {
otherConstraint.type to false
}
ConstraintKind.UPPER -> {
/*
* Creating a captured type isn't needed due to its future approximation to `Nothing` or itself
* Example:
* targetVariable = TypeVariable(A)
* baseConstraint = LOWER(TypeVariable(B))
* otherConstraint = UPPER(Number)
* incorporatedConstraint = Approx(CapturedType(out Number)) <: TypeVariable(A) => Nothing <: TypeVariable(A)
* TODO: implement this for generics and captured types
*/
if (baseConstraint.kind == ConstraintKind.LOWER && !isBaseGenericType && !isOtherCapturedType) {
nothingType() to false
} else if (baseConstraint.kind == ConstraintKind.UPPER && !isBaseGenericType && !isOtherCapturedType) {
otherConstraint.type to false
} else {
createCapturedType(
createTypeArgument(otherConstraint.type, TypeVariance.OUT),
listOf(otherConstraint.type),
null,
CaptureStatus.FOR_INCORPORATION
) to true
}
}
ConstraintKind.LOWER -> {
/*
* Creating a captured type isn't needed due to its future approximation to `Any?` or itself
* Example:
* targetVariable = TypeVariable(A)
* baseConstraint = UPPER(TypeVariable(B))
* otherConstraint = LOWER(Number)
* incorporatedConstraint = TypeVariable(A) <: Approx(CapturedType(in Number)) => TypeVariable(A) <: Any?
* TODO: implement this for generics and captured types
*/
if (baseConstraint.kind == ConstraintKind.UPPER && !isBaseGenericType && !isOtherCapturedType) {
nullableAnyType() to false
} else if (baseConstraint.kind == ConstraintKind.LOWER && !isBaseGenericType && !isOtherCapturedType) {
otherConstraint.type to false
} else {
createCapturedType(
createTypeArgument(otherConstraint.type, TypeVariance.IN),
emptyList(),
otherConstraint.type,
CaptureStatus.FOR_INCORPORATION
) to true
}
}
}
approximateIfNeededAndAddNewConstraint(baseConstraint, type, targetVariable, otherVariable, otherConstraint, needApproximation)
}
private fun Context.addNewConstraint(
targetVariable: TypeVariableMarker,
baseConstraint: Constraint,
otherVariable: TypeVariableMarker,
otherConstraint: Constraint,
newConstraint: KotlinTypeMarker,
isSubtype: Boolean
) {
if (targetVariable in getNestedTypeVariables(newConstraint)) return
val isUsefulForNullabilityConstraint =
isPotentialUsefulNullabilityConstraint(newConstraint, otherConstraint.type, otherConstraint.kind)
val isFromVariableFixation = baseConstraint.position.from is FixVariableConstraintPosition<*>
|| otherConstraint.position.from is FixVariableConstraintPosition<*>
if (!otherConstraint.kind.isEqual() &&
!isUsefulForNullabilityConstraint &&
!isFromVariableFixation &&
!containsConstrainingTypeWithoutProjection(newConstraint, otherConstraint)
) return
if (trivialConstraintTypeInferenceOracle.isGeneratedConstraintTrivial(
baseConstraint, otherConstraint, newConstraint, isSubtype
)
) return
val derivedFrom = SmartSet.create(baseConstraint.derivedFrom).also { it.addAll(otherConstraint.derivedFrom) }
if (otherVariable in derivedFrom) return
derivedFrom.add(otherVariable)
val kind = if (isSubtype) ConstraintKind.LOWER else ConstraintKind.UPPER
val inputTypePosition = baseConstraint.position.from as? OnlyInputTypeConstraintPosition
val isNewConstraintUsefulForNullability = isUsefulForNullabilityConstraint && newConstraint.isNullableNothing()
val isOtherConstraintUsefulForNullability = otherConstraint.isNullabilityConstraint && otherConstraint.type.isNullableNothing()
val isNullabilityConstraint = isNewConstraintUsefulForNullability || isOtherConstraintUsefulForNullability
val constraintContext = ConstraintContext(kind, derivedFrom, inputTypePosition, isNullabilityConstraint)
addNewIncorporatedConstraint(targetVariable, newConstraint, constraintContext)
}
private fun Context.containsConstrainingTypeWithoutProjection(
newConstraint: KotlinTypeMarker,
otherConstraint: Constraint
): Boolean {
return getNestedArguments(newConstraint).any {
it.getType().typeConstructor() == otherConstraint.type.typeConstructor() && it.getVariance() == TypeVariance.INV
}
}
private fun Context.isPotentialUsefulNullabilityConstraint(
newConstraint: KotlinTypeMarker,
otherConstraint: KotlinTypeMarker,
kind: ConstraintKind
): Boolean {
if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(newConstraint)) return false
val otherConstraintCanAddNullabilityToNewOne =
!newConstraint.isNullableType() && otherConstraint.isNullableType() && kind == ConstraintKind.LOWER
val newConstraintCanAddNullabilityToOtherOne =
newConstraint.isNullableType() && !otherConstraint.isNullableType() && kind == ConstraintKind.UPPER
return otherConstraintCanAddNullabilityToNewOne || newConstraintCanAddNullabilityToOtherOne
}
private fun Context.getNestedTypeVariables(type: KotlinTypeMarker): List<TypeVariableMarker> =
getNestedArguments(type).mapNotNullTo(SmartList()) { getTypeVariable(it.getType().typeConstructor()) }
private fun KotlinTypeMarker.substitute(c: Context, typeVariable: TypeVariableMarker, value: KotlinTypeMarker): KotlinTypeMarker {
val substitutor = c.typeSubstitutorByTypeConstructor(mapOf(typeVariable.freshTypeConstructor(c) to value))
return substitutor.safeSubstitute(c, this)
}
private fun approximateCapturedTypes(type: KotlinTypeMarker, toSuper: Boolean): KotlinTypeMarker =
if (toSuper) typeApproximator.approximateToSuperType(type, TypeApproximatorConfiguration.IncorporationConfiguration) ?: type
else typeApproximator.approximateToSubType(type, TypeApproximatorConfiguration.IncorporationConfiguration) ?: type
}
private fun TypeSystemInferenceExtensionContext.getNestedArguments(type: KotlinTypeMarker): List<TypeArgumentMarker> {
val result = SmartList<TypeArgumentMarker>()
val stack = ArrayDeque<TypeArgumentMarker>()
when (type) {
is FlexibleTypeMarker -> {
stack.push(createTypeArgument(type.lowerBound(), TypeVariance.INV))
stack.push(createTypeArgument(type.upperBound(), TypeVariance.INV))
}
else -> stack.push(createTypeArgument(type, TypeVariance.INV))
}
stack.push(createTypeArgument(type, TypeVariance.INV))
val addArgumentsToStack = { projectedType: KotlinTypeMarker ->
for (argumentIndex in 0 until projectedType.argumentsCount()) {
stack.add(projectedType.getArgument(argumentIndex))
}
}
while (!stack.isEmpty()) {
val typeProjection = stack.pop()
if (typeProjection.isStarProjection()) continue
result.add(typeProjection)
when (val projectedType = typeProjection.getType()) {
is FlexibleTypeMarker -> {
addArgumentsToStack(projectedType.lowerBound())
addArgumentsToStack(projectedType.upperBound())
}
else -> addArgumentsToStack(projectedType)
}
}
return result
}
@@ -0,0 +1,351 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind.*
import org.jetbrains.kotlin.types.AbstractTypeApproximator
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartList
import kotlin.math.max
class ConstraintInjector(
val constraintIncorporator: ConstraintIncorporator,
val typeApproximator: AbstractTypeApproximator
) {
private val ALLOWED_DEPTH_DELTA_FOR_INCORPORATION = 1
interface Context : TypeSystemInferenceExtensionContext {
val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
var maxTypeDepthFromInitialConstraints: Int
val notFixedTypeVariables: MutableMap<TypeConstructorMarker, MutableVariableWithConstraints>
val fixedTypeVariables: MutableMap<TypeConstructorMarker, KotlinTypeMarker>
fun addInitialConstraint(initialConstraint: InitialConstraint)
fun addError(error: ConstraintSystemError)
}
fun addInitialSubtypeConstraint(c: Context, lowerType: KotlinTypeMarker, upperType: KotlinTypeMarker, position: ConstraintPosition) {
val initialConstraint = InitialConstraint(lowerType, upperType, UPPER, position).also { c.addInitialConstraint(it) }
updateAllowedTypeDepth(c, lowerType)
updateAllowedTypeDepth(c, upperType)
addSubTypeConstraintAndIncorporateIt(
c,
lowerType,
upperType,
TypeCheckerContext(c, IncorporationConstraintPosition(position, initialConstraint))
)
}
fun addInitialEqualityConstraint(c: Context, a: KotlinTypeMarker, b: KotlinTypeMarker, position: ConstraintPosition) {
val initialConstraint = InitialConstraint(a, b, EQUALITY, position).also { c.addInitialConstraint(it) }
updateAllowedTypeDepth(c, a)
updateAllowedTypeDepth(c, b)
val typeCheckerContext = TypeCheckerContext(c, IncorporationConstraintPosition(position, initialConstraint))
addSubTypeConstraintAndIncorporateIt(c, a, b, typeCheckerContext)
addSubTypeConstraintAndIncorporateIt(c, b, a, typeCheckerContext)
}
private fun addSubTypeConstraintAndIncorporateIt(
c: Context,
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
typeCheckerContext: TypeCheckerContext
) {
typeCheckerContext.setConstrainingTypesToPrintDebugInfo(lowerType, upperType)
typeCheckerContext.runIsSubtypeOf(lowerType, upperType)
while (typeCheckerContext.hasConstraintsToProcess()) {
for ((typeVariable, constraint) in typeCheckerContext.extractAllConstraints()!!) {
if (c.shouldWeSkipConstraint(typeVariable, constraint)) continue
val constraints =
c.notFixedTypeVariables[typeVariable.freshTypeConstructor(c)] ?: typeCheckerContext.fixedTypeVariable(typeVariable)
// it is important, that we add constraint here(not inside TypeCheckerContext), because inside incorporation we read constraints
constraints.addConstraint(constraint)?.let {
if (!constraint.isNullabilityConstraint) {
constraintIncorporator.incorporate(typeCheckerContext, typeVariable, it)
}
}
}
val contextOps = c as? ConstraintSystemOperation
if (!typeCheckerContext.hasConstraintsToProcess() ||
(contextOps != null && c.notFixedTypeVariables.all { typeVariable ->
typeVariable.value.constraints.any { constraint ->
constraint.kind == EQUALITY && contextOps.isProperType(constraint.type)
}
})
) {
break
}
}
}
private fun updateAllowedTypeDepth(c: Context, initialType: KotlinTypeMarker) = with(c) {
c.maxTypeDepthFromInitialConstraints = max(c.maxTypeDepthFromInitialConstraints, initialType.typeDepth())
}
private fun Context.shouldWeSkipConstraint(typeVariable: TypeVariableMarker, constraint: Constraint): Boolean {
assert(constraint.kind != EQUALITY)
val constraintType = constraint.type
if (constraintType.typeConstructor() == typeVariable.freshTypeConstructor()) {
if (constraintType.lowerBoundIfFlexible().isMarkedNullable() && constraint.kind == LOWER) return false // T? <: T
return true // T <: T(?!)
}
if (constraint.position.from is DeclaredUpperBoundConstraintPosition<*> &&
constraint.kind == UPPER && constraintType.isNullableAny()
) {
return true // T <: Any?
}
return false
}
private fun Context.isAllowedType(type: KotlinTypeMarker) =
type.typeDepth() <= maxTypeDepthFromInitialConstraints + ALLOWED_DEPTH_DELTA_FOR_INCORPORATION
private inner class TypeCheckerContext(val c: Context, val position: IncorporationConstraintPosition) :
AbstractTypeCheckerContextForConstraintSystem(), ConstraintIncorporator.Context, TypeSystemInferenceExtensionContext by c {
// We use `var` intentionally to avoid extra allocations as this property is quite "hot"
private var possibleNewConstraints: MutableList<Pair<TypeVariableMarker, Constraint>>? = null
private var baseLowerType = position.initialConstraint.a
private var baseUpperType = position.initialConstraint.b
private var isIncorporatingConstraintFromDeclaredUpperBound = false
fun extractAllConstraints() = possibleNewConstraints.also { possibleNewConstraints = null }
fun addPossibleNewConstraint(variable: TypeVariableMarker, constraint: Constraint) {
if (possibleNewConstraints == null) {
possibleNewConstraints = SmartList()
}
possibleNewConstraints!!.add(variable to constraint)
}
fun hasConstraintsToProcess() = possibleNewConstraints != null
fun setConstrainingTypesToPrintDebugInfo(lowerType: KotlinTypeMarker, upperType: KotlinTypeMarker) {
baseLowerType = lowerType
baseUpperType = upperType
}
val baseContext: AbstractTypeCheckerContext = newBaseTypeCheckerContext(isErrorTypeEqualsToAnything, isStubTypeEqualsToAnything)
override fun substitutionSupertypePolicy(type: SimpleTypeMarker): SupertypesPolicy {
return baseContext.substitutionSupertypePolicy(type)
}
override fun areEqualTypeConstructors(a: TypeConstructorMarker, b: TypeConstructorMarker): Boolean {
return baseContext.areEqualTypeConstructors(a, b)
}
override fun prepareType(type: KotlinTypeMarker): KotlinTypeMarker {
return baseContext.prepareType(type)
}
override fun refineType(type: KotlinTypeMarker): KotlinTypeMarker {
return with(constraintIncorporator.utilContext) {
type.refineType()
}
}
fun runIsSubtypeOf(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
shouldTryUseDifferentFlexibilityForUpperType: Boolean = false,
isFromNullabilityConstraint: Boolean = false
) {
fun isSubtypeOf(upperType: KotlinTypeMarker) =
AbstractTypeChecker.isSubtypeOf(
this@TypeCheckerContext as AbstractTypeCheckerContext,
lowerType,
upperType,
isFromNullabilityConstraint
)
if (!isSubtypeOf(upperType)) {
// todo improve error reporting -- add information about base types
if (shouldTryUseDifferentFlexibilityForUpperType && upperType.isSimpleType()) {
/*
* Please don't reuse this logic.
* It's necessary to solve constraint systems when flexibility isn't propagated through a type variable.
* It's OK in the old inference because it uses already substituted types, that are with the correct flexibility.
*/
require(upperType is SimpleTypeMarker)
val flexibleUpperType = createFlexibleType(upperType, upperType.withNullability(true))
if (!isSubtypeOf(flexibleUpperType)) {
c.addError(NewConstraintError(lowerType, flexibleUpperType, position))
}
} else {
c.addError(NewConstraintError(lowerType, upperType, position))
}
}
}
// from AbstractTypeCheckerContextForConstraintSystem
override fun isMyTypeVariable(type: SimpleTypeMarker): Boolean =
c.allTypeVariables.containsKey(type.typeConstructor())
override fun addUpperConstraint(typeVariable: TypeConstructorMarker, superType: KotlinTypeMarker) =
addConstraint(typeVariable, superType, UPPER)
override fun addLowerConstraint(
typeVariable: TypeConstructorMarker,
subType: KotlinTypeMarker,
isFromNullabilityConstraint: Boolean
) = addConstraint(typeVariable, subType, LOWER, isFromNullabilityConstraint)
private fun isCapturedTypeFromSubtyping(type: KotlinTypeMarker) =
when ((type as? CapturedTypeMarker)?.captureStatus()) {
null, CaptureStatus.FROM_EXPRESSION -> false
CaptureStatus.FOR_SUBTYPING -> true
CaptureStatus.FOR_INCORPORATION ->
error("Captured type for incorporation shouldn't escape from incorporation: $type\n" + renderBaseConstraint())
}
private fun addConstraint(
typeVariableConstructor: TypeConstructorMarker,
type: KotlinTypeMarker,
kind: ConstraintKind,
isFromNullabilityConstraint: Boolean = false
) {
val typeVariable = c.allTypeVariables[typeVariableConstructor]
?: error("Should by type variableConstructor: $typeVariableConstructor. ${c.allTypeVariables.values}")
addNewIncorporatedConstraint(
typeVariable,
type,
ConstraintContext(kind, emptySet(), isNullabilityConstraint = isFromNullabilityConstraint)
)
}
private fun addNewIncorporatedConstraintFromDeclaredUpperBound(runIsSubtypeOf: Runnable) {
isIncorporatingConstraintFromDeclaredUpperBound = true
runIsSubtypeOf.run()
isIncorporatingConstraintFromDeclaredUpperBound = false
}
// from ConstraintIncorporator.Context
override fun addNewIncorporatedConstraint(
lowerType: KotlinTypeMarker,
upperType: KotlinTypeMarker,
shouldTryUseDifferentFlexibilityForUpperType: Boolean,
isFromNullabilityConstraint: Boolean,
isFromDeclaredUpperBound: Boolean
) {
if (lowerType === upperType) return
if (c.isAllowedType(lowerType) && c.isAllowedType(upperType)) {
fun runIsSubtypeOf() =
runIsSubtypeOf(lowerType, upperType, shouldTryUseDifferentFlexibilityForUpperType, isFromNullabilityConstraint)
if (isFromDeclaredUpperBound) addNewIncorporatedConstraintFromDeclaredUpperBound(::runIsSubtypeOf) else runIsSubtypeOf()
}
}
override fun addNewIncorporatedConstraint(
typeVariable: TypeVariableMarker,
type: KotlinTypeMarker,
constraintContext: ConstraintContext
) {
val (kind, derivedFrom, inputTypePosition, isNullabilityConstraint) = constraintContext
var targetType = type
if (targetType.isUninferredParameter()) {
// there already should be an error, so there is no point in reporting one more
return
}
if (targetType.isError()) {
c.addError(ConstrainingTypeIsError(typeVariable, targetType, position))
return
}
if (type.contains(this::isCapturedTypeFromSubtyping)) {
// TypeVariable <: type -> if TypeVariable <: subType => TypeVariable <: type
if (kind == UPPER) {
val subType =
typeApproximator.approximateToSubType(type, TypeApproximatorConfiguration.SubtypeCapturedTypesApproximation)
if (subType != null) {
targetType = subType
}
}
if (kind == LOWER) {
val superType =
typeApproximator.approximateToSuperType(type, TypeApproximatorConfiguration.SubtypeCapturedTypesApproximation)
if (superType != null) { // todo rethink error reporting for Any cases
targetType = superType
}
}
if (targetType === type) {
c.addError(CapturedTypeFromSubtyping(typeVariable, type, position))
return
}
}
val position = if (isIncorporatingConstraintFromDeclaredUpperBound) position.copy(isFromDeclaredUpperBound = true) else position
val newConstraint = Constraint(
kind, targetType, position,
derivedFrom = derivedFrom,
isNullabilityConstraint = isNullabilityConstraint,
inputTypePositionBeforeIncorporation = inputTypePosition
)
addPossibleNewConstraint(typeVariable, newConstraint)
}
override val allTypeVariablesWithConstraints: Collection<VariableWithConstraints>
get() = c.notFixedTypeVariables.values
override fun getTypeVariable(typeConstructor: TypeConstructorMarker): TypeVariableMarker? {
val typeVariable = c.allTypeVariables[typeConstructor]
if (typeVariable != null && !c.notFixedTypeVariables.containsKey(typeConstructor)) {
fixedTypeVariable(typeVariable)
}
return typeVariable
}
override fun getConstraintsForVariable(typeVariable: TypeVariableMarker) =
c.notFixedTypeVariables[typeVariable.freshTypeConstructor()]?.constraints
?: fixedTypeVariable(typeVariable)
fun fixedTypeVariable(variable: TypeVariableMarker): Nothing {
error(
"Type variable $variable should not be fixed!\n" +
renderBaseConstraint()
)
}
private fun renderBaseConstraint() = "Base constraint: $baseLowerType <: $baseUpperType from position: $position"
}
}
data class ConstraintContext(
val kind: ConstraintKind,
val derivedFrom: Set<TypeVariableMarker>,
val inputTypePositionBeforeIncorporation: OnlyInputTypeConstraintPosition? = null,
val isNullabilityConstraint: Boolean
)
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintSystemError
import org.jetbrains.kotlin.resolve.calls.inference.model.FixVariableConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
interface ConstraintSystemCompletionContext : VariableFixationFinder.Context, ResultTypeResolver.Context {
val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
override val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
override val postponedTypeVariables: List<TypeVariableMarker>
fun getBuilder(): ConstraintSystemBuilder
// type can be proper if it not contains not fixed type variables
fun canBeProper(type: KotlinTypeMarker): Boolean
fun containsOnlyFixedOrPostponedVariables(type: KotlinTypeMarker): Boolean
// mutable operations
fun addError(error: ConstraintSystemError)
fun fixVariable(variable: TypeVariableMarker, resultType: KotlinTypeMarker, position: FixVariableConstraintPosition<*>)
fun asConstraintSystemCompletionContext(): ConstraintSystemCompletionContext
}
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
enum class ConstraintSystemCompletionMode {
FULL,
PARTIAL,
UNTIL_FIRST_LAMBDA
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
/*
* Functions from this context can not be moved to TypeSystemInferenceExtensionContext, because
* it's classic implementation, ClassicTypeSystemContext lays in :core:descriptors,
* but we need access classes from :compiler:resolution for this function implementation
*/
interface ConstraintSystemUtilContext {
fun TypeVariableMarker.shouldBeFlexible(): Boolean
fun TypeVariableMarker.hasOnlyInputTypesAttribute(): Boolean
fun KotlinTypeMarker.unCapture(): KotlinTypeMarker
fun TypeVariableMarker.isReified(): Boolean
fun KotlinTypeMarker.refineType(): KotlinTypeMarker
}
@@ -0,0 +1,224 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator
import org.jetbrains.kotlin.resolve.calls.inference.components.TypeVariableDirectionCalculator.ResolveDirection
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.types.AbstractTypeApproximator
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.model.*
class ResultTypeResolver(
val typeApproximator: AbstractTypeApproximator,
val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle
) {
interface Context : TypeSystemInferenceExtensionContext {
fun isProperType(type: KotlinTypeMarker): Boolean
fun buildNotFixedVariablesToStubTypesSubstitutor(): TypeSubstitutorMarker
fun isReified(variable: TypeVariableMarker): Boolean
}
fun findResultType(c: Context, variableWithConstraints: VariableWithConstraints, direction: ResolveDirection): KotlinTypeMarker {
findResultTypeOrNull(c, variableWithConstraints, direction)?.let { return it }
// no proper constraints
return run {
if (direction == ResolveDirection.TO_SUBTYPE) c.nothingType() else c.nullableAnyType()
}
}
private fun findResultTypeOrNull(
c: Context,
variableWithConstraints: VariableWithConstraints,
direction: ResolveDirection
): KotlinTypeMarker? {
findResultIfThereIsEqualsConstraint(c, variableWithConstraints)?.let { return it }
val subType = c.findSubType(variableWithConstraints)
val superType = c.findSuperType(variableWithConstraints)
return if (direction == ResolveDirection.TO_SUBTYPE || direction == ResolveDirection.UNKNOWN) {
c.resultType(subType, superType, variableWithConstraints)
} else {
c.resultType(superType, subType, variableWithConstraints)
}
}
private fun Context.resultType(
firstCandidate: KotlinTypeMarker?,
secondCandidate: KotlinTypeMarker?,
variableWithConstraints: VariableWithConstraints
): KotlinTypeMarker? {
if (firstCandidate == null || secondCandidate == null) return firstCandidate ?: secondCandidate
specialResultForIntersectionType(firstCandidate, secondCandidate)?.let { intersectionWithAlternative ->
return intersectionWithAlternative
}
if (isSuitableType(firstCandidate, variableWithConstraints)) return firstCandidate
return if (isSuitableType(secondCandidate, variableWithConstraints)) {
secondCandidate
} else {
firstCandidate
}
}
private fun Context.specialResultForIntersectionType(
firstCandidate: KotlinTypeMarker,
secondCandidate: KotlinTypeMarker,
): KotlinTypeMarker? {
if (firstCandidate.typeConstructor().isIntersection()) {
if (!AbstractTypeChecker.isSubtypeOf(this, firstCandidate.toPublicType(), secondCandidate.toPublicType())) {
return createTypeWithAlternativeForIntersectionResult(firstCandidate, secondCandidate)
}
}
return null
}
private fun KotlinTypeMarker.toPublicType(): KotlinTypeMarker =
typeApproximator.approximateToSuperType(this, TypeApproximatorConfiguration.PublicDeclaration) ?: this
private fun Context.isSuitableType(resultType: KotlinTypeMarker, variableWithConstraints: VariableWithConstraints): Boolean {
val filteredConstraints = variableWithConstraints.constraints.filter { isProperTypeForFixation(it.type) }
for (constraint in filteredConstraints) {
if (!checkConstraint(this, constraint.type, constraint.kind, resultType)) return false
}
if (!trivialConstraintTypeInferenceOracle.isSuitableResultedType(resultType)) {
if (resultType.isNullableType() && checkSingleLowerNullabilityConstraint(filteredConstraints)) return false
if (isReified(variableWithConstraints.typeVariable)) return false
}
return true
}
private fun checkSingleLowerNullabilityConstraint(constraints: List<Constraint>): Boolean {
return constraints.singleOrNull { it.kind.isLower() }?.isNullabilityConstraint ?: false
}
private fun Context.findSubType(variableWithConstraints: VariableWithConstraints): KotlinTypeMarker? {
val lowerConstraintTypes = prepareLowerConstraints(variableWithConstraints.constraints)
if (lowerConstraintTypes.isNotEmpty()) {
val types = sinkIntegerLiteralTypes(lowerConstraintTypes)
var commonSuperType = computeCommonSuperType(types)
if (commonSuperType.contains { it is StubTypeMarker }) {
val typesWithoutStubs = types.filter { lowerType ->
!lowerType.contains { it is StubTypeMarker }
}
if (typesWithoutStubs.isNotEmpty()) {
commonSuperType = computeCommonSuperType(typesWithoutStubs)
} else {
return null
}
}
/**
*
* fun <T> Array<out T>.intersect(other: Iterable<T>) {
* val set = toMutableSet()
* set.retainAll(other)
* }
* fun <X> Array<out X>.toMutableSet(): MutableSet<X> = ...
* fun <Y> MutableCollection<in Y>.retainAll(elements: Iterable<Y>) {}
*
* Here, when we solve type system for `toMutableSet` we have the following constrains:
* Array<C(out T)> <: Array<out X> => C(out X) <: T.
* If we fix it to T = C(out X) then return type of `toMutableSet()` will be `MutableSet<C(out X)>`
* and type of variable `set` will be `MutableSet<out T>` and the following line will have contradiction.
*
* To fix this problem when we fix variable, we will approximate captured types before fixation.
*
*/
return typeApproximator.approximateToSuperType(
commonSuperType,
TypeApproximatorConfiguration.InternalTypesApproximation
) ?: commonSuperType
}
return null
}
private fun Context.computeCommonSuperType(types: List<KotlinTypeMarker>): KotlinTypeMarker =
with(NewCommonSuperTypeCalculator) { commonSuperType(types) }
private fun Context.prepareLowerConstraints(constraints: List<Constraint>): List<KotlinTypeMarker> {
var atLeastOneProper = false
var atLeastOneNonProper = false
val lowerConstraintTypes = mutableListOf<KotlinTypeMarker>()
for (constraint in constraints) {
if (constraint.kind != ConstraintKind.LOWER) continue
val type = constraint.type
lowerConstraintTypes.add(type)
if (isProperTypeForFixation(type)) {
atLeastOneProper = true
} else {
atLeastOneNonProper = true
}
}
if (!atLeastOneProper) return emptyList()
if (!atLeastOneNonProper) return lowerConstraintTypes
val notFixedToStubTypesSubstitutor = buildNotFixedVariablesToStubTypesSubstitutor()
return lowerConstraintTypes.map { if (isProperTypeForFixation(it)) it else notFixedToStubTypesSubstitutor.safeSubstitute(it) }
}
private fun Context.sinkIntegerLiteralTypes(types: List<KotlinTypeMarker>): List<KotlinTypeMarker> {
return types.sortedBy { type ->
val containsILT = type.contains { it.asSimpleType()?.isIntegerLiteralType() ?: false }
if (containsILT) 1 else 0
}
}
private fun Context.findSuperType(variableWithConstraints: VariableWithConstraints): KotlinTypeMarker? {
val upperConstraints =
variableWithConstraints.constraints.filter { it.kind == ConstraintKind.UPPER && this@findSuperType.isProperTypeForFixation(it.type) }
if (upperConstraints.isNotEmpty()) {
val upperType = intersectTypes(upperConstraints.map { it.type })
return typeApproximator.approximateToSubType(
upperType,
TypeApproximatorConfiguration.InternalTypesApproximation
) ?: upperType
}
return null
}
private fun Context.isProperTypeForFixation(type: KotlinTypeMarker): Boolean =
isProperTypeForFixation(type) { isProperType(it) }
private fun findResultIfThereIsEqualsConstraint(c: Context, variableWithConstraints: VariableWithConstraints): KotlinTypeMarker? =
with(c) {
val properEqualityConstraints = variableWithConstraints.constraints.filter {
it.kind == ConstraintKind.EQUALITY && c.isProperTypeForFixation(it.type)
}
return c.representativeFromEqualityConstraints(properEqualityConstraints)
}
// Discriminate integer literal types as they are less specific than separate integer types (Int, Short...)
private fun Context.representativeFromEqualityConstraints(constraints: List<Constraint>): KotlinTypeMarker? {
if (constraints.isEmpty()) return null
val constraintTypes = constraints.map { it.type }
val nonLiteralTypes = constraintTypes.filter { !it.typeConstructor().isIntegerLiteralTypeConstructor() }
return nonLiteralTypes.singleBestRepresentative()
?: constraintTypes.singleBestRepresentative()
?: constraintTypes.first() // seems like constraint system has contradiction
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.SimpleTypeMarker
import org.jetbrains.kotlin.types.model.TypeSystemInferenceExtensionContext
import org.jetbrains.kotlin.types.model.TypeSystemInferenceExtensionContextDelegate
class TrivialConstraintTypeInferenceOracle private constructor(context: TypeSystemInferenceExtensionContext) :
TypeSystemInferenceExtensionContext by context {
// This constructor is used for injection only in old FE
constructor(context: TypeSystemInferenceExtensionContextDelegate) : this(context as TypeSystemInferenceExtensionContext)
// The idea is to add knowledge that constraint `Nothing(?) <: T` is quite useless and
// it's totally fine to go and resolve postponed argument without fixation T to Nothing(?).
// In other words, constraint `Nothing(?) <: T` is *not* proper
fun isNotInterestingConstraint(constraint: Constraint): Boolean {
return constraint.kind == ConstraintKind.LOWER && constraint.type.typeConstructor().isNothingConstructor()
}
// This function controls the choice between sub and super result type
// Even that Nothing(?) is the most specific type for subtype, it doesn't bring valuable information to the user,
// therefore it is discriminated in favor of supertype
fun isSuitableResultedType(
resultType: KotlinTypeMarker
): Boolean {
return !resultType.typeConstructor().isNothingConstructor()
}
// It's possible to generate Nothing-like constraints inside incorporation mechanism:
// For instance, when two type variables are in subtyping relation `T <: K`, after incorporation
// there will be constraint `approximation(out K) <: K` => `Nothing <: K`, which is innocent
// but can change result of the constraint system.
// Therefore, here we avoid adding such trivial constraints to have stable constraint system
fun isGeneratedConstraintTrivial(
baseConstraint: Constraint,
otherConstraint: Constraint,
generatedConstraintType: KotlinTypeMarker,
isSubtype: Boolean
): Boolean {
if (isSubtype && (generatedConstraintType.isNothing() || generatedConstraintType.isFlexibleNothing())) return true
if (!isSubtype && generatedConstraintType.isNullableAny()) return true
// If types from constraints that will be used to generate new constraint already contains `Nothing(?)`,
// then we can't decide that resulting constraint will be useless
if (baseConstraint.type.contains { it.isNothingOrNullableNothing() }) return false
if (otherConstraint.type.contains { it.isNothingOrNullableNothing() }) return false
// It's important to preserve constraints with nullable Nothing: `Nothing? <: T` (see implicitNothingConstraintFromReturn.kt test)
if (generatedConstraintType.containsOnlyNonNullableNothing()) return true
return false
}
private fun KotlinTypeMarker.isNothingOrNullableNothing(): Boolean =
typeConstructor().isNothingConstructor()
private fun KotlinTypeMarker.containsOnlyNonNullableNothing(): Boolean =
contains {
(it.isNothing() || it.isFlexibleNothing()) &&
!(it is SimpleTypeMarker && it.typeConstructor().isNothingConstructor() && it.isMarkedNullable())
}
companion object {
fun create(context: TypeSystemInferenceExtensionContext) = TrivialConstraintTypeInferenceOracle(context)
}
}
@@ -0,0 +1,155 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartSet
class TypeVariableDependencyInformationProvider(
private val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>,
private val postponedKtPrimitives: List<PostponedResolvedAtomMarker>,
private val topLevelType: KotlinTypeMarker?,
private val typeSystemContext: TypeSystemInferenceExtensionContext
) {
/*
* Not oriented edges
* TypeVariable(A) has UPPER(Function1<TypeVariable(B), R>) => A and B are related deeply
*/
private val deepTypeVariableDependencies: MutableMap<TypeConstructorMarker, MutableSet<TypeConstructorMarker>> = hashMapOf()
/*
* Not oriented edges
* TypeVariable(A) has UPPER(TypeVariable(B)) => A and B are related shallowly
*/
private val shallowTypeVariableDependencies: MutableMap<TypeConstructorMarker, MutableSet<TypeConstructorMarker>> = hashMapOf()
// Oriented edges
private val postponeArgumentsEdges: MutableMap<TypeConstructorMarker, MutableSet<TypeConstructorMarker>> = hashMapOf()
private val relatedToAllOutputTypes: MutableSet<TypeConstructorMarker> = hashSetOf()
private val relatedToTopLevelType: MutableSet<TypeConstructorMarker> = hashSetOf()
init {
computeConstraintEdges()
computePostponeArgumentsEdges()
computeRelatedToAllOutputTypes()
computeRelatedToTopLevelType()
}
fun isVariableRelatedToTopLevelType(variable: TypeConstructorMarker) = relatedToTopLevelType.contains(variable)
fun isVariableRelatedToAnyOutputType(variable: TypeConstructorMarker) = relatedToAllOutputTypes.contains(variable)
fun getDeeplyDependentVariables(variable: TypeConstructorMarker) = deepTypeVariableDependencies[variable]
fun getShallowlyDependentVariables(variable: TypeConstructorMarker) = shallowTypeVariableDependencies[variable]
fun areVariablesDependentShallowly(a: TypeConstructorMarker, b: TypeConstructorMarker): Boolean {
if (a == b) return true
val shallowDependencies = shallowTypeVariableDependencies[a] ?: return false
return shallowDependencies.any { it == b } ||
shallowTypeVariableDependencies.values.any { dependencies -> a in dependencies && b in dependencies }
}
private fun computeConstraintEdges() {
fun addConstraintEdgeForDeepDependency(from: TypeConstructorMarker, to: TypeConstructorMarker) {
deepTypeVariableDependencies.getOrPut(from) { linkedSetOf() }.add(to)
deepTypeVariableDependencies.getOrPut(to) { linkedSetOf() }.add(from)
}
fun addConstraintEdgeForShallowDependency(from: TypeConstructorMarker, to: TypeConstructorMarker) {
shallowTypeVariableDependencies.getOrPut(from) { linkedSetOf() }.add(to)
shallowTypeVariableDependencies.getOrPut(to) { linkedSetOf() }.add(from)
}
for (variableWithConstraints in notFixedTypeVariables.values) {
val from = variableWithConstraints.typeVariable.freshTypeConstructor(typeSystemContext)
for (constraint in variableWithConstraints.constraints) {
val constraintTypeConstructor = constraint.type.typeConstructor(typeSystemContext)
constraint.type.forAllMyTypeVariables {
if (isMyTypeVariable(it)) {
addConstraintEdgeForDeepDependency(from, it)
}
}
if (isMyTypeVariable(constraintTypeConstructor)) {
addConstraintEdgeForShallowDependency(from, constraintTypeConstructor)
}
}
}
}
private fun computePostponeArgumentsEdges() {
fun addPostponeArgumentsEdges(from: TypeConstructorMarker, to: TypeConstructorMarker) {
postponeArgumentsEdges.getOrPut(from) { hashSetOf() }.add(to)
}
for (argument in postponedKtPrimitives) {
if (argument.analyzed) continue
val typeVariablesInOutputType = SmartSet.create<TypeConstructorMarker>()
(argument.outputType ?: continue).forAllMyTypeVariables { typeVariablesInOutputType.add(it) }
if (typeVariablesInOutputType.isEmpty()) continue
for (inputType in argument.inputTypes) {
inputType.forAllMyTypeVariables { from ->
for (to in typeVariablesInOutputType) {
addPostponeArgumentsEdges(from, to)
}
}
}
}
}
private fun computeRelatedToAllOutputTypes() {
for (argument in postponedKtPrimitives) {
if (argument.analyzed) continue
(argument.outputType ?: continue).forAllMyTypeVariables {
addAllRelatedNodes(relatedToAllOutputTypes, it, includePostponedEdges = false)
}
}
}
private fun computeRelatedToTopLevelType() {
if (topLevelType == null) return
topLevelType.forAllMyTypeVariables {
addAllRelatedNodes(relatedToTopLevelType, it, includePostponedEdges = true)
}
}
private fun isMyTypeVariable(typeConstructor: TypeConstructorMarker) = notFixedTypeVariables.containsKey(typeConstructor)
private fun KotlinTypeMarker.forAllMyTypeVariables(action: (TypeConstructorMarker) -> Unit) =
with(typeSystemContext) {
contains {
val typeConstructor = it.typeConstructor()
if (isMyTypeVariable(typeConstructor)) action(typeConstructor)
false
}
}
private fun getConstraintEdges(from: TypeConstructorMarker): Set<TypeConstructorMarker> = deepTypeVariableDependencies[from] ?: emptySet()
private fun getPostponeEdges(from: TypeConstructorMarker): Set<TypeConstructorMarker> = postponeArgumentsEdges[from] ?: emptySet()
private fun addAllRelatedNodes(to: MutableSet<TypeConstructorMarker>, node: TypeConstructorMarker, includePostponedEdges: Boolean) {
if (to.add(node)) {
for (relatedNode in getConstraintEdges(node)) {
addAllRelatedNodes(to, relatedNode, includePostponedEdges)
}
if (includePostponedEdges) {
for (relatedNode in getPostponeEdges(node)) {
addAllRelatedNodes(to, relatedNode, includePostponedEdges)
}
}
}
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.FlexibleTypeMarker
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.SimpleTypeMarker
import org.jetbrains.kotlin.types.model.TypeVariance
import org.jetbrains.kotlin.utils.SmartList
private typealias Variable = VariableWithConstraints
class TypeVariableDirectionCalculator(
private val c: VariableFixationFinder.Context,
private val postponedKtPrimitives: List<PostponedResolvedAtomMarker>,
topLevelType: KotlinTypeMarker
) {
enum class ResolveDirection {
TO_SUBTYPE,
TO_SUPERTYPE,
UNKNOWN
}
data class NodeWithDirection(val variableWithConstraints: VariableWithConstraints, val direction: ResolveDirection) {
override fun toString() = "$variableWithConstraints to $direction"
}
private val directions = HashMap<Variable, ResolveDirection>()
init {
setupDirections(topLevelType)
}
fun getDirection(typeVariable: Variable): ResolveDirection =
directions.getOrDefault(typeVariable, ResolveDirection.UNKNOWN)
private fun setupDirections(topReturnType: KotlinTypeMarker) {
topReturnType.visitType(ResolveDirection.TO_SUBTYPE) { variableWithConstraints, direction ->
enterToNode(variableWithConstraints, direction)
}
for (postponedArgument in postponedKtPrimitives) {
for (inputType in postponedArgument.inputTypes) {
inputType.visitType(ResolveDirection.TO_SUBTYPE) { variableWithConstraints, direction ->
enterToNode(variableWithConstraints, direction)
}
}
}
}
private fun enterToNode(variable: Variable, direction: ResolveDirection) {
if (direction == ResolveDirection.UNKNOWN) return
val previous = directions[variable]
if (previous != null) {
if (previous != direction) {
directions[variable] = ResolveDirection.UNKNOWN
}
return
}
directions[variable] = direction
for ((otherVariable, otherDirection) in getConstraintDependencies(variable, direction)) {
enterToNode(otherVariable, otherDirection)
}
}
private fun getConstraintDependencies(
variable: Variable,
direction: ResolveDirection
): List<NodeWithDirection> =
SmartList<NodeWithDirection>().also { result ->
for (constraint in variable.constraints) {
if (!isInterestingConstraint(direction, constraint)) continue
constraint.type.visitType(direction) { nodeVariable, nodeDirection ->
result.add(NodeWithDirection(nodeVariable, nodeDirection))
}
}
}
private fun isInterestingConstraint(direction: ResolveDirection, constraint: Constraint): Boolean =
!(direction == ResolveDirection.TO_SUBTYPE && constraint.kind == ConstraintKind.UPPER) &&
!(direction == ResolveDirection.TO_SUPERTYPE && constraint.kind == ConstraintKind.LOWER)
private fun KotlinTypeMarker.visitType(
startDirection: ResolveDirection,
action: (variable: Variable, direction: ResolveDirection) -> Unit
) = when (this) {
is SimpleTypeMarker -> visitType(startDirection, action)
is FlexibleTypeMarker -> {
with(c) {
lowerBound().visitType(startDirection, action)
upperBound().visitType(startDirection, action)
}
}
else -> error("?!")
}
private fun SimpleTypeMarker.visitType(
startDirection: ResolveDirection,
action: (variable: Variable, direction: ResolveDirection) -> Unit
): Unit = with(c) {
val constructor = typeConstructor()
if (constructor.isIntersection()) {
constructor.supertypes().forEach {
it.visitType(startDirection, action)
}
return
}
if (argumentsCount() == 0) {
c.notFixedTypeVariables[constructor]?.let {
action(it, startDirection)
}
return
}
if (constructor.parametersCount() != argumentsCount()) return // incorrect type
for (index in 0 until constructor.parametersCount()) {
val parameter = constructor.getParameter(index)
val argument = getArgument(index)
if (argument.isStarProjection()) continue
val variance = AbstractTypeChecker.effectiveVariance(parameter.getVariance(), argument.getVariance()) ?: TypeVariance.INV
val innerDirection = when (variance) {
TypeVariance.INV -> ResolveDirection.UNKNOWN
TypeVariance.OUT -> startDirection
TypeVariance.IN -> startDirection.opposite()
}
argument.getType().visitType(innerDirection, action)
}
}
private fun ResolveDirection.opposite() = when (this) {
ResolveDirection.UNKNOWN -> ResolveDirection.UNKNOWN
ResolveDirection.TO_SUPERTYPE -> ResolveDirection.TO_SUBTYPE
ResolveDirection.TO_SUBTYPE -> ResolveDirection.TO_SUPERTYPE
}
}
@@ -0,0 +1,153 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemCompletionMode.PARTIAL
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.DeclaredUpperBoundConstraintPosition
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtomMarker
import org.jetbrains.kotlin.types.model.*
class VariableFixationFinder(
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle
) {
interface Context : TypeSystemInferenceExtensionContext {
val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
val postponedTypeVariables: List<TypeVariableMarker>
fun isReified(variable: TypeVariableMarker): Boolean
}
data class VariableForFixation(
val variable: TypeConstructorMarker,
val hasProperConstraint: Boolean,
val hasOnlyTrivialProperConstraint: Boolean = false
)
fun findFirstVariableForFixation(
c: Context,
allTypeVariables: List<TypeConstructorMarker>,
postponedKtPrimitives: List<PostponedResolvedAtomMarker>,
completionMode: ConstraintSystemCompletionMode,
topLevelType: KotlinTypeMarker
): VariableForFixation? = c.findTypeVariableForFixation(allTypeVariables, postponedKtPrimitives, completionMode, topLevelType)
enum class TypeVariableFixationReadiness {
FORBIDDEN,
WITHOUT_PROPER_ARGUMENT_CONSTRAINT, // proper constraint from arguments -- not from upper bound for type parameters
WITH_COMPLEX_DEPENDENCY, // if type variable T has constraint with non fixed type variable inside (non-top-level): T <: Foo<S>
WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS, // proper trivial constraint from arguments, Nothing <: T
RELATED_TO_ANY_OUTPUT_TYPE,
FROM_INCORPORATION_OF_DECLARED_UPPER_BOUND,
READY_FOR_FIXATION,
READY_FOR_FIXATION_REIFIED,
}
private fun Context.getTypeVariableReadiness(
variable: TypeConstructorMarker,
dependencyProvider: TypeVariableDependencyInformationProvider
): TypeVariableFixationReadiness = when {
!notFixedTypeVariables.contains(variable) ||
dependencyProvider.isVariableRelatedToTopLevelType(variable) -> TypeVariableFixationReadiness.FORBIDDEN
!variableHasProperArgumentConstraints(variable) -> TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT
hasDependencyToOtherTypeVariables(variable) -> TypeVariableFixationReadiness.WITH_COMPLEX_DEPENDENCY
variableHasTrivialOrNonProperConstraints(variable) -> TypeVariableFixationReadiness.WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS
dependencyProvider.isVariableRelatedToAnyOutputType(variable) -> TypeVariableFixationReadiness.RELATED_TO_ANY_OUTPUT_TYPE
variableHasOnlyIncorporatedConstraintsFromDeclaredUpperBound(variable) ->
TypeVariableFixationReadiness.FROM_INCORPORATION_OF_DECLARED_UPPER_BOUND
isReified(variable) -> TypeVariableFixationReadiness.READY_FOR_FIXATION_REIFIED
else -> TypeVariableFixationReadiness.READY_FOR_FIXATION
}
fun isTypeVariableHasProperConstraint(context: Context, typeVariable: TypeConstructorMarker): Boolean {
return with(context) {
val dependencyProvider = TypeVariableDependencyInformationProvider(
notFixedTypeVariables, emptyList(), topLevelType = null, context
)
when (getTypeVariableReadiness(typeVariable, dependencyProvider)) {
TypeVariableFixationReadiness.FORBIDDEN, TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT -> false
else -> true
}
}
}
private fun Context.variableHasTrivialOrNonProperConstraints(variable: TypeConstructorMarker): Boolean {
return notFixedTypeVariables[variable]?.constraints?.all { constraint ->
val isProperConstraint = isProperArgumentConstraint(constraint)
isProperConstraint && trivialConstraintTypeInferenceOracle.isNotInterestingConstraint(constraint) || !isProperConstraint
} ?: false
}
private fun Context.variableHasOnlyIncorporatedConstraintsFromDeclaredUpperBound(variable: TypeConstructorMarker): Boolean {
val constraints = notFixedTypeVariables[variable]?.constraints ?: return false
return constraints.filter { isProperArgumentConstraint(it) }.all { it.position.isFromDeclaredUpperBound }
}
private fun Context.findTypeVariableForFixation(
allTypeVariables: List<TypeConstructorMarker>,
postponedArguments: List<PostponedResolvedAtomMarker>,
completionMode: ConstraintSystemCompletionMode,
topLevelType: KotlinTypeMarker
): VariableForFixation? {
if (allTypeVariables.isEmpty()) return null
val dependencyProvider = TypeVariableDependencyInformationProvider(
notFixedTypeVariables, postponedArguments, topLevelType.takeIf { completionMode == PARTIAL }, this
)
val candidate = allTypeVariables.maxByOrNull { getTypeVariableReadiness(it, dependencyProvider) } ?: return null
return when (getTypeVariableReadiness(candidate, dependencyProvider)) {
TypeVariableFixationReadiness.FORBIDDEN -> null
TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT -> VariableForFixation(candidate, false)
TypeVariableFixationReadiness.WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS ->
VariableForFixation(candidate, hasProperConstraint = true, hasOnlyTrivialProperConstraint = true)
else -> VariableForFixation(candidate, true)
}
}
private fun Context.hasDependencyToOtherTypeVariables(typeVariable: TypeConstructorMarker): Boolean {
for (constraint in notFixedTypeVariables[typeVariable]?.constraints ?: return false) {
val dependencyPresenceCondition = { type: KotlinTypeMarker ->
type.typeConstructor() != typeVariable && notFixedTypeVariables.containsKey(type.typeConstructor())
}
if (constraint.type.lowerBoundIfFlexible().argumentsCount() != 0 && constraint.type.contains(dependencyPresenceCondition))
return true
}
return false
}
private fun Context.variableHasProperArgumentConstraints(variable: TypeConstructorMarker): Boolean =
notFixedTypeVariables[variable]?.constraints?.any { isProperArgumentConstraint(it) } ?: false
private fun Context.isProperArgumentConstraint(c: Constraint) =
isProperType(c.type)
&& c.position.initialConstraint.position !is DeclaredUpperBoundConstraintPosition<*>
&& !c.isNullabilityConstraint
private fun Context.isProperType(type: KotlinTypeMarker): Boolean =
isProperTypeForFixation(type) { t -> !t.contains { notFixedTypeVariables.containsKey(it.typeConstructor()) } }
private fun Context.isReified(variable: TypeConstructorMarker): Boolean =
notFixedTypeVariables[variable]?.typeVariable?.let { isReified(it) } ?: false
}
inline fun TypeSystemInferenceExtensionContext.isProperTypeForFixation(
type: KotlinTypeMarker,
isProper: (KotlinTypeMarker) -> Boolean
): Boolean {
if (!isProper(type)) return false
if (type.isCapturedType()) {
val projection = (type as? SimpleTypeMarker)?.asCapturedType()?.typeConstructorProjection() ?: return true
if (projection.isStarProjection()) return true
if (!isProper(projection.getType())) return false
}
return true
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.model
import org.jetbrains.kotlin.resolve.calls.tower.ResolutionCandidateApplicability
import org.jetbrains.kotlin.resolve.calls.tower.ResolutionCandidateApplicability.*
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
interface OnlyInputTypeConstraintPosition
sealed class ConstraintPosition
abstract class ExplicitTypeParameterConstraintPosition<T>(val typeArgument: T) : ConstraintPosition(), OnlyInputTypeConstraintPosition {
override fun toString(): String = "TypeParameter $typeArgument"
}
abstract class ExpectedTypeConstraintPosition<T>(val topLevelCall: T) : ConstraintPosition(), OnlyInputTypeConstraintPosition {
override fun toString(): String = "ExpectedType for call $topLevelCall"
}
abstract class DeclaredUpperBoundConstraintPosition<T>(val typeParameter: T) : ConstraintPosition() {
override fun toString(): String = "DeclaredUpperBound $typeParameter"
}
abstract class ArgumentConstraintPosition<T>(val argument: T) : ConstraintPosition(), OnlyInputTypeConstraintPosition {
override fun toString(): String = "Argument $argument"
}
abstract class ReceiverConstraintPosition<T>(val argument: T) : ConstraintPosition(), OnlyInputTypeConstraintPosition {
override fun toString(): String = "Receiver $argument"
}
abstract class FixVariableConstraintPosition<T>(val variable: TypeVariableMarker, val resolvedAtom: T) : ConstraintPosition() {
override fun toString(): String = "Fix variable $variable"
}
abstract class KnownTypeParameterConstraintPosition<T : KotlinTypeMarker>(val typeArgument: T) : ConstraintPosition() {
override fun toString(): String = "TypeArgument $typeArgument"
}
abstract class LHSArgumentConstraintPosition<T, R>(
val argument: T,
val receiver: R
) : ConstraintPosition() {
override fun toString(): String {
return "LHS receiver $receiver"
}
}
abstract class LambdaArgumentConstraintPosition<T>(val lambda: T) : ConstraintPosition() {
override fun toString(): String {
return "LambdaArgument $lambda"
}
}
abstract class DelegatedPropertyConstraintPosition<T>(val topLevelCall: T) : ConstraintPosition() {
override fun toString(): String = "Constraint from call $topLevelCall for delegated property"
}
data class IncorporationConstraintPosition(
val from: ConstraintPosition,
val initialConstraint: InitialConstraint,
var isFromDeclaredUpperBound: Boolean = false
) : ConstraintPosition() {
override fun toString(): String = "Incorporate $initialConstraint from position $from"
}
object CoroutinePosition : ConstraintPosition() {
override fun toString(): String = "for coroutine call"
}
// TODO: should be used only in SimpleConstraintSystemImpl
object SimpleConstraintSystemConstraintPosition : ConstraintPosition()
// ------------------------------------------------ Errors ------------------------------------------------
sealed class ConstraintSystemError(val applicability: ResolutionCandidateApplicability)
class NewConstraintError(
val lowerType: KotlinTypeMarker,
val upperType: KotlinTypeMarker,
val position: IncorporationConstraintPosition
) : ConstraintSystemError(if (position.from is ReceiverConstraintPosition<*>) INAPPLICABLE_WRONG_RECEIVER else INAPPLICABLE)
class CapturedTypeFromSubtyping(
val typeVariable: TypeVariableMarker,
val constraintType: KotlinTypeMarker,
val position: ConstraintPosition
) : ConstraintSystemError(INAPPLICABLE)
abstract class NotEnoughInformationForTypeParameter<T>(
val typeVariable: TypeVariableMarker,
val resolvedAtom: T
) : ConstraintSystemError(INAPPLICABLE)
class ConstrainingTypeIsError(
val typeVariable: TypeVariableMarker,
val constraintType: KotlinTypeMarker,
val position: IncorporationConstraintPosition
) : ConstraintSystemError(INAPPLICABLE)
class OnlyInputTypesDiagnostic(val typeVariable: TypeVariableMarker) : ConstraintSystemError(INAPPLICABLE)
object LowerPriorityToPreserveCompatibility : ConstraintSystemError(RESOLVED_NEED_PRESERVE_COMPATIBILITY)
@@ -0,0 +1,144 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.model
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
/**
* Every type variable can be in the following states:
* - not fixed => there is several constraints for this type variable(possible no one).
* for this type variable we have VariableWithConstraints in map notFixedTypeVariables
* - fixed to proper type or not proper type. For such type variable there is no VariableWithConstraints in notFixedTypeVariables.
* Also we should guaranty that there is no other constraints in other VariableWithConstraints which depends on this fixed type variable.
*
* Note: fixedTypeVariables can contains a proper and not proper type.
*
* Fixing procedure(to proper types). First of all we should determinate fixing order.
* After it, for every type variable we do the following:
* - determinate result proper type
* - add equality constraint, for example: T = Int
* - run incorporation and generate all new constraints
* - after is we remove VariableWithConstraints for type variable T from map notFixedTypeVariables
* - also we remove all constraint in other variable which contains T
* - add result type to fixedTypeVariables.
*
* Note fixing procedure to not proper type the same. The only difference in determination result type.
*
*/
interface ConstraintStorage {
val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
val initialConstraints: List<InitialConstraint>
val maxTypeDepthFromInitialConstraints: Int
val errors: List<ConstraintSystemError>
val hasContradiction: Boolean
val fixedTypeVariables: Map<TypeConstructorMarker, KotlinTypeMarker>
val postponedTypeVariables: List<TypeVariableMarker>
object Empty : ConstraintStorage {
override val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker> get() = emptyMap()
override val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints> get() = emptyMap()
override val initialConstraints: List<InitialConstraint> get() = emptyList()
override val maxTypeDepthFromInitialConstraints: Int get() = 1
override val errors: List<ConstraintSystemError> get() = emptyList()
override val hasContradiction: Boolean get() = false
override val fixedTypeVariables: Map<TypeConstructorMarker, KotlinTypeMarker> get() = emptyMap()
override val postponedTypeVariables: List<TypeVariableMarker> get() = emptyList()
}
}
enum class ConstraintKind {
LOWER,
UPPER,
EQUALITY;
fun isLower(): Boolean = this == LOWER
fun isUpper(): Boolean = this == UPPER
fun isEqual(): Boolean = this == EQUALITY
fun opposite() = when (this) {
LOWER -> UPPER
UPPER -> LOWER
EQUALITY -> EQUALITY
}
}
class Constraint(
val kind: ConstraintKind,
val type: KotlinTypeMarker, // flexible types here is allowed
val position: IncorporationConstraintPosition,
val typeHashCode: Int = type.hashCode(),
val derivedFrom: Set<TypeVariableMarker>,
val isNullabilityConstraint: Boolean,
val inputTypePositionBeforeIncorporation: OnlyInputTypeConstraintPosition? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Constraint
if (typeHashCode != other.typeHashCode) return false
if (kind != other.kind) return false
if (position != other.position) return false
if (type != other.type) return false
return true
}
override fun hashCode() = typeHashCode
override fun toString() = "$kind($type) from $position"
}
interface VariableWithConstraints {
val typeVariable: TypeVariableMarker
val constraints: List<Constraint>
}
class InitialConstraint(
val a: KotlinTypeMarker,
val b: KotlinTypeMarker,
val constraintKind: ConstraintKind, // see [checkConstraint]
val position: ConstraintPosition
) {
override fun toString(): String {
val sign =
when (constraintKind) {
ConstraintKind.EQUALITY -> "=="
ConstraintKind.LOWER -> ":>"
ConstraintKind.UPPER -> "<:"
}
return "$a $sign $b from $position"
}
}
//fun InitialConstraint.checkConstraint(substitutor: TypeSubstitutor): Boolean {
// val newA = substitutor.substitute(a)
// val newB = substitutor.substitute(b)
// return checkConstraint(newB as KotlinTypeMarker, constraintKind, newA as KotlinTypeMarker)
//}
fun checkConstraint(
context: TypeCheckerProviderContext,
constraintType: KotlinTypeMarker,
constraintKind: ConstraintKind,
resultType: KotlinTypeMarker
): Boolean {
val typeChecker = AbstractTypeChecker
return when (constraintKind) {
ConstraintKind.EQUALITY -> typeChecker.equalTypes(context, constraintType, resultType)
ConstraintKind.LOWER -> typeChecker.isSubtypeOf(context, constraintType, resultType)
ConstraintKind.UPPER -> typeChecker.isSubtypeOf(context, resultType, constraintType)
}
}
@@ -0,0 +1,205 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.model
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemUtilContext
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.trimToSize
private typealias Context = TypeSystemInferenceExtensionContext
class MutableVariableWithConstraints private constructor(
private val context: Context,
override val typeVariable: TypeVariableMarker,
constraints: List<Constraint>? // assume simplified and deduplicated
) : VariableWithConstraints {
constructor(context: Context, typeVariable: TypeVariableMarker) : this(context, typeVariable, null)
constructor(context: Context, other: VariableWithConstraints) : this(context, other.typeVariable, other.constraints)
override val constraints: List<Constraint>
get() {
if (simplifiedConstraints == null) {
simplifiedConstraints = mutableConstraints.simplifyConstraints()
}
return simplifiedConstraints!!
}
// see @OnlyInputTypes annotation
fun getProjectedInputCallTypes(utilContext: ConstraintSystemUtilContext): Collection<KotlinTypeMarker> {
return with(utilContext) {
mutableConstraints
.mapNotNullTo(SmartList()) {
if (it.position.from is OnlyInputTypeConstraintPosition || it.inputTypePositionBeforeIncorporation != null)
it.type.unCapture()
else null
}
}
}
private val mutableConstraints = if (constraints == null) SmartList() else SmartList(constraints)
private var simplifiedConstraints: SmartList<Constraint>? = mutableConstraints
// return new actual constraint, if this constraint is new
fun addConstraint(constraint: Constraint): Constraint? {
val isLowerAndFlexibleTypeWithDefNotNullLowerBound = constraint.isLowerAndFlexibleTypeWithDefNotNullLowerBound()
for (previousConstraint in constraints) {
if (previousConstraint.typeHashCode == constraint.typeHashCode
&& previousConstraint.type == constraint.type
&& previousConstraint.isNullabilityConstraint == constraint.isNullabilityConstraint
) {
if (newConstraintIsUseless(previousConstraint, constraint)) return null
val isMatchingForSimplification = when (previousConstraint.kind) {
ConstraintKind.LOWER -> constraint.kind.isUpper()
ConstraintKind.UPPER -> constraint.kind.isLower()
ConstraintKind.EQUALITY -> true
}
if (isMatchingForSimplification) {
val actualConstraint = Constraint(
ConstraintKind.EQUALITY,
constraint.type,
constraint.position,
constraint.typeHashCode,
derivedFrom = constraint.derivedFrom,
isNullabilityConstraint = false
)
mutableConstraints.add(actualConstraint)
simplifiedConstraints = null
return actualConstraint
}
}
if (isLowerAndFlexibleTypeWithDefNotNullLowerBound &&
previousConstraint.isStrongerThanLowerAndFlexibleTypeWithDefNotNullLowerBound(constraint)
) {
return null
}
}
mutableConstraints.add(constraint)
if (simplifiedConstraints != null && simplifiedConstraints !== mutableConstraints) {
simplifiedConstraints!!.add(constraint)
}
if (simplifiedConstraints != null && isLowerAndFlexibleTypeWithDefNotNullLowerBound) {
simplifiedConstraints = null
}
return constraint
}
// This method should be used only for transaction in constraint system
// shouldRemove should give true only for tail elements
internal fun removeLastConstraints(shouldRemove: (Constraint) -> Boolean) {
mutableConstraints.trimToSize(mutableConstraints.indexOfLast { !shouldRemove(it) } + 1)
if (simplifiedConstraints !== mutableConstraints) {
simplifiedConstraints = null
}
}
// This method should be used only when constraint system has state COMPLETION
internal fun removeConstrains(shouldRemove: (Constraint) -> Boolean) {
mutableConstraints.removeAll(shouldRemove)
if (simplifiedConstraints !== mutableConstraints) {
simplifiedConstraints = null
}
}
private fun newConstraintIsUseless(old: Constraint, new: Constraint): Boolean {
// Constraints from declared upper bound are quite special -- they aren't considered as a proper ones
// In other words, user-defined constraints have "higher" priority and here we're trying not to loose them
if (old.position.from is DeclaredUpperBoundConstraintPosition<*> && new.position.from !is DeclaredUpperBoundConstraintPosition<*>)
return false
return when (old.kind) {
ConstraintKind.EQUALITY -> true
ConstraintKind.LOWER -> new.kind.isLower()
ConstraintKind.UPPER -> new.kind.isUpper()
}
}
private fun SmartList<Constraint>.simplifyConstraints(): SmartList<Constraint> =
simplifyLowerConstraints().simplifyEqualityConstraints()
private fun SmartList<Constraint>.simplifyLowerConstraints(): SmartList<Constraint> {
val usefulConstraints = SmartList<Constraint>()
for (constraint in this) {
if (!constraint.isLowerAndFlexibleTypeWithDefNotNullLowerBound()) {
usefulConstraints.add(constraint)
continue
}
// Now we have to check that some constraint T!!.T? <: K is useless or not
// If there is constraint T..T? <: K, then the original one (T!!.T?) is useless
// This is so because CST(T..T?, T!!..T?) == CST(T..T?)
val thereIsStrongerConstraint = this.any { it.isStrongerThanLowerAndFlexibleTypeWithDefNotNullLowerBound(constraint) }
if (!thereIsStrongerConstraint) {
usefulConstraints.add(constraint)
}
}
return usefulConstraints
}
// Such constraint is applicable for simplification
private fun Constraint.isLowerAndFlexibleTypeWithDefNotNullLowerBound(): Boolean {
return with(context) {
kind == ConstraintKind.LOWER && type.isFlexible() && type.lowerBoundIfFlexible().isDefinitelyNotNullType()
}
}
private fun Constraint.isStrongerThanLowerAndFlexibleTypeWithDefNotNullLowerBound(other: Constraint): Boolean {
if (this === other) return false
if (typeHashCode != other.typeHashCode || kind == ConstraintKind.UPPER) return false
with(context) {
if (!type.isFlexible() || !other.type.isFlexible()) return false
val otherLowerBound = other.type.lowerBoundIfFlexible()
if (!otherLowerBound.isDefinitelyNotNullType()) return false
require(otherLowerBound is DefinitelyNotNullTypeMarker)
val thisLowerBound = type.lowerBoundIfFlexible()
val thisUpperBound = type.upperBoundIfFlexible()
val otherUpperBound = other.type.upperBoundIfFlexible()
return thisLowerBound == otherLowerBound.original() && thisUpperBound == otherUpperBound
}
}
private fun SmartList<Constraint>.simplifyEqualityConstraints(): SmartList<Constraint> {
val equalityConstraints = filter { it.kind == ConstraintKind.EQUALITY }.groupBy { it.typeHashCode }
return when {
equalityConstraints.isEmpty() -> this
else -> filterTo(SmartList()) { isUsefulConstraint(it, equalityConstraints) }
}
}
private fun isUsefulConstraint(constraint: Constraint, equalityConstraints: Map<Int, List<Constraint>>): Boolean {
if (constraint.kind == ConstraintKind.EQUALITY) return true
return equalityConstraints[constraint.typeHashCode]?.none { it.type == constraint.type } ?: true
}
override fun toString(): String {
return "Constraints for $typeVariable"
}
}
internal class MutableConstraintStorage : ConstraintStorage {
override val allTypeVariables: MutableMap<TypeConstructorMarker, TypeVariableMarker> = LinkedHashMap()
override val notFixedTypeVariables: MutableMap<TypeConstructorMarker, MutableVariableWithConstraints> = LinkedHashMap()
override val initialConstraints: MutableList<InitialConstraint> = SmartList()
override var maxTypeDepthFromInitialConstraints: Int = 1
override val errors: MutableList<ConstraintSystemError> = SmartList()
override val hasContradiction: Boolean get() = errors.any { !it.applicability.isSuccess }
override val fixedTypeVariables: MutableMap<TypeConstructorMarker, KotlinTypeMarker> = LinkedHashMap()
override val postponedTypeVariables: MutableList<TypeVariableMarker> = SmartList()
}
@@ -0,0 +1,386 @@
/*
* Copyright 2010-2020 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.resolve.calls.inference.model
import org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzerContext
import org.jetbrains.kotlin.resolve.calls.inference.*
import org.jetbrains.kotlin.resolve.calls.inference.components.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.model.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.kotlin.utils.addToStdlib.trimToSize
import kotlin.math.max
class NewConstraintSystemImpl(
private val constraintInjector: ConstraintInjector,
val typeSystemContext: TypeSystemInferenceExtensionContext
) : TypeSystemInferenceExtensionContext by typeSystemContext,
NewConstraintSystem,
ConstraintSystemBuilder,
ConstraintInjector.Context,
ResultTypeResolver.Context,
ConstraintSystemCompletionContext,
PostponedArgumentsAnalyzerContext
{
private val utilContext = constraintInjector.constraintIncorporator.utilContext
private val storage = MutableConstraintStorage()
private var state = State.BUILDING
private val typeVariablesTransaction: MutableList<TypeVariableMarker> = SmartList()
private val properTypesCache: MutableSet<KotlinTypeMarker> = SmartSet.create()
private val notProperTypesCache: MutableSet<KotlinTypeMarker> = SmartSet.create()
private enum class State {
BUILDING,
TRANSACTION,
FREEZED,
COMPLETION
}
private fun checkState(a: State) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
checkState(*arrayOf(a))
}
private fun checkState(a: State, b: State) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
checkState(*arrayOf(a, b))
}
private fun checkState(a: State, b: State, c: State) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
checkState(*arrayOf(a, b, c))
}
private fun checkState(a: State, b: State, c: State, d: State) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
checkState(*arrayOf(a, b, c, d))
}
private fun checkState(vararg allowedState: State) {
if (!AbstractTypeChecker.RUN_SLOW_ASSERTIONS) return
assert(state in allowedState) {
"State $state is not allowed. AllowedStates: ${allowedState.joinToString()}"
}
}
override val errors: List<ConstraintSystemError>
get() = storage.errors
override fun getBuilder() = apply { checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION) }
override fun asReadOnlyStorage(): ConstraintStorage {
checkState(State.BUILDING, State.FREEZED)
state = State.FREEZED
return storage
}
override fun asConstraintSystemCompleterContext() = apply { checkState(State.BUILDING) }
override fun asPostponedArgumentsAnalyzerContext() = apply { checkState(State.BUILDING) }
override fun asConstraintSystemCompletionContext(): ConstraintSystemCompletionContext = apply { checkState(State.BUILDING) }
// ConstraintSystemOperation
override fun registerVariable(variable: TypeVariableMarker) {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
transactionRegisterVariable(variable)
storage.allTypeVariables[variable.freshTypeConstructor()] = variable
notProperTypesCache.clear()
storage.notFixedTypeVariables[variable.freshTypeConstructor()] = MutableVariableWithConstraints(this, variable)
}
override fun markPostponedVariable(variable: TypeVariableMarker) {
storage.postponedTypeVariables += variable
}
override fun unmarkPostponedVariable(variable: TypeVariableMarker) {
storage.postponedTypeVariables -= variable
}
override fun removePostponedVariables() {
storage.postponedTypeVariables.clear()
}
override fun addSubtypeConstraint(lowerType: KotlinTypeMarker, upperType: KotlinTypeMarker, position: ConstraintPosition) =
constraintInjector.addInitialSubtypeConstraint(
apply { checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION) },
lowerType,
upperType,
position
)
override fun addEqualityConstraint(a: KotlinTypeMarker, b: KotlinTypeMarker, position: ConstraintPosition) =
constraintInjector.addInitialEqualityConstraint(
apply { checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION) },
a,
b,
position
)
override fun getProperSuperTypeConstructors(type: KotlinTypeMarker): List<TypeConstructorMarker> {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
val variableWithConstraints = notFixedTypeVariables[type.typeConstructor()] ?: return listOf(type.typeConstructor())
return variableWithConstraints.constraints.mapNotNull {
if (it.kind == ConstraintKind.LOWER) return@mapNotNull null
it.type.typeConstructor().takeUnless { allTypeVariables.containsKey(it) }
}
}
// ConstraintSystemBuilder
private fun transactionRegisterVariable(variable: TypeVariableMarker) {
if (state != State.TRANSACTION) return
typeVariablesTransaction.add(variable)
}
private fun closeTransaction(beforeState: State, beforeTypeVariables: Int) {
checkState(State.TRANSACTION)
typeVariablesTransaction.trimToSize(beforeTypeVariables)
state = beforeState
}
override fun runTransaction(runOperations: ConstraintSystemOperation.() -> Boolean): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
val beforeState = state
val beforeInitialConstraintCount = storage.initialConstraints.size
val beforeErrorsCount = storage.errors.size
val beforeMaxTypeDepthFromInitialConstraints = storage.maxTypeDepthFromInitialConstraints
val beforeTypeVariablesTransactionSize = typeVariablesTransaction.size
state = State.TRANSACTION
// typeVariablesTransaction is clear
if (runOperations()) {
closeTransaction(beforeState, beforeTypeVariablesTransactionSize)
return true
}
for (addedTypeVariable in typeVariablesTransaction.subList(beforeTypeVariablesTransactionSize, typeVariablesTransaction.size)) {
storage.allTypeVariables.remove(addedTypeVariable.freshTypeConstructor())
storage.notFixedTypeVariables.remove(addedTypeVariable.freshTypeConstructor())
}
storage.maxTypeDepthFromInitialConstraints = beforeMaxTypeDepthFromInitialConstraints
storage.errors.trimToSize(beforeErrorsCount)
val addedInitialConstraints = storage.initialConstraints.subList(beforeInitialConstraintCount, storage.initialConstraints.size)
val shouldRemove = { c: Constraint -> addedInitialConstraints.contains(c.position.initialConstraint) }
for (variableWithConstraint in storage.notFixedTypeVariables.values) {
variableWithConstraint.removeLastConstraints(shouldRemove)
}
addedInitialConstraints.clear() // remove constraint from storage.initialConstraints
closeTransaction(beforeState, beforeTypeVariablesTransactionSize)
return false
}
// ConstraintSystemBuilder, KotlinConstraintSystemCompleter.Context
override val hasContradiction: Boolean
get() = storage.hasContradiction.also {
checkState(
State.FREEZED,
State.BUILDING,
State.COMPLETION,
State.TRANSACTION
)
}
override fun addOtherSystem(otherSystem: ConstraintStorage) {
if (otherSystem.allTypeVariables.isNotEmpty()) {
otherSystem.allTypeVariables.forEach {
transactionRegisterVariable(it.value)
}
storage.allTypeVariables.putAll(otherSystem.allTypeVariables)
notProperTypesCache.clear()
}
for ((variable, constraints) in otherSystem.notFixedTypeVariables) {
notFixedTypeVariables[variable] = MutableVariableWithConstraints(this, constraints)
}
storage.initialConstraints.addAll(otherSystem.initialConstraints)
storage.maxTypeDepthFromInitialConstraints =
max(storage.maxTypeDepthFromInitialConstraints, otherSystem.maxTypeDepthFromInitialConstraints)
storage.errors.addAll(otherSystem.errors)
storage.fixedTypeVariables.putAll(otherSystem.fixedTypeVariables)
storage.postponedTypeVariables.addAll(otherSystem.postponedTypeVariables)
}
// ResultTypeResolver.Context, ConstraintSystemBuilder
override fun isProperType(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
if (storage.allTypeVariables.isEmpty()) return true
if (notProperTypesCache.contains(type)) return false
if (properTypesCache.contains(type)) return true
return isProperTypeImpl(type).also {
(if (it) properTypesCache else notProperTypesCache).add(type)
}
}
private fun isProperTypeImpl(type: KotlinTypeMarker): Boolean =
!type.contains {
val capturedType = it.asSimpleType()?.asCapturedType()
// TODO: change NewCapturedType to markered one for FE-IR
val typeToCheck = if (capturedType is CapturedTypeMarker && capturedType.captureStatus() == CaptureStatus.FROM_EXPRESSION)
capturedType.typeConstructorProjection().takeUnless { projection -> projection.isStarProjection() }?.getType()
else
it
if (typeToCheck == null) return@contains false
storage.allTypeVariables.containsKey(typeToCheck.typeConstructor())
}
override fun isTypeVariable(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return notFixedTypeVariables.containsKey(type.typeConstructor())
}
override fun isPostponedTypeVariable(typeVariable: TypeVariableMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return typeVariable in postponedTypeVariables
}
// ConstraintInjector.Context, KotlinConstraintSystemCompleter.Context
override val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
get() {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.allTypeVariables
}
override var maxTypeDepthFromInitialConstraints: Int
get() = storage.maxTypeDepthFromInitialConstraints
set(value) {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
storage.maxTypeDepthFromInitialConstraints = value
}
override fun addInitialConstraint(initialConstraint: InitialConstraint) {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
storage.initialConstraints.add(initialConstraint)
}
// ConstraintInjector.Context, FixationOrderCalculator.Context
override val notFixedTypeVariables: MutableMap<TypeConstructorMarker, MutableVariableWithConstraints>
get() {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.notFixedTypeVariables
}
override val fixedTypeVariables: MutableMap<TypeConstructorMarker, KotlinTypeMarker>
get() {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.fixedTypeVariables
}
override val postponedTypeVariables: List<TypeVariableMarker>
get() {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.postponedTypeVariables
}
// ConstraintInjector.Context, KotlinConstraintSystemCompleter.Context
override fun addError(error: ConstraintSystemError) {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
storage.errors.add(error)
}
// KotlinConstraintSystemCompleter.Context
// TODO: simplify this: do only substitution a fixing type variable rather than running of subtyping and full incorporation
override fun fixVariable(variable: TypeVariableMarker, resultType: KotlinTypeMarker, position: FixVariableConstraintPosition<*>) {
checkState(State.BUILDING, State.COMPLETION)
constraintInjector.addInitialEqualityConstraint(
this, variable.defaultType(), resultType, position
)
val freshTypeConstructor = variable.freshTypeConstructor()
val variableWithConstraints = notFixedTypeVariables.remove(freshTypeConstructor)
checkOnlyInputTypesAnnotation(variableWithConstraints, resultType)
for (variableWithConstraint in notFixedTypeVariables.values) {
variableWithConstraint.removeConstrains {
it.type.contains { it.typeConstructor() == freshTypeConstructor }
}
}
storage.fixedTypeVariables[freshTypeConstructor] = resultType
}
private fun checkOnlyInputTypesAnnotation(
variableWithConstraints: MutableVariableWithConstraints?,
resultType: KotlinTypeMarker
) {
if (variableWithConstraints == null) return
val variableHasOnlyInputTypes = with(utilContext) { variableWithConstraints.typeVariable.hasOnlyInputTypesAttribute() }
if (!variableHasOnlyInputTypes) return
val resultTypeIsInputType = variableWithConstraints.getProjectedInputCallTypes(utilContext).any { inputType ->
if (AbstractTypeChecker.equalTypes(this, resultType, inputType)) return@any true
val constructor = inputType.typeConstructor()
constructor.isIntersection() && constructor.supertypes().any { AbstractTypeChecker.equalTypes(this, resultType, it) }
}
if (!resultTypeIsInputType) {
addError(OnlyInputTypesDiagnostic(variableWithConstraints.typeVariable))
}
}
// KotlinConstraintSystemCompleter.Context, PostponedArgumentsAnalyzer.Context
override fun canBeProper(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION)
return !type.contains { storage.notFixedTypeVariables.containsKey(it.typeConstructor()) }
}
override fun containsOnlyFixedOrPostponedVariables(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION)
return !type.contains {
val typeConstructor = it.typeConstructor()
val variable = storage.notFixedTypeVariables[typeConstructor]?.typeVariable
variable !in storage.postponedTypeVariables && storage.notFixedTypeVariables.containsKey(typeConstructor)
}
}
// PostponedArgumentsAnalyzer.Context
override fun buildCurrentSubstitutor(): TypeSubstitutorMarker {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return buildCurrentSubstitutor(emptyMap())
}
override fun buildCurrentSubstitutor(additionalBindings: Map<TypeConstructorMarker, StubTypeMarker>): TypeSubstitutorMarker {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.buildCurrentSubstitutor(this, additionalBindings)
}
override fun buildNotFixedVariablesToStubTypesSubstitutor(): TypeSubstitutorMarker {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage.buildNotFixedVariablesToNonSubtypableTypesSubstitutor(this)
}
// ResultTypeResolver.Context, VariableFixationFinder.Context
override fun isReified(variable: TypeVariableMarker): Boolean {
return with(utilContext) { variable.isReified() }
}
override fun bindingStubsForPostponedVariables(): Map<TypeVariableMarker, StubTypeMarker> {
checkState(State.BUILDING, State.COMPLETION)
// TODO: SUB
return storage.postponedTypeVariables.associateWith { createStubType(it) }
}
override fun currentStorage(): ConstraintStorage {
checkState(State.BUILDING, State.COMPLETION, State.TRANSACTION)
return storage
}
// PostponedArgumentsAnalyzer.Context
override fun hasUpperOrEqualUnitConstraint(type: KotlinTypeMarker): Boolean {
checkState(State.BUILDING, State.COMPLETION, State.FREEZED)
val constraints = storage.notFixedTypeVariables[type.typeConstructor()]?.constraints ?: return false
return constraints.any { (it.kind == ConstraintKind.UPPER || it.kind == ConstraintKind.EQUALITY) && it.type.isUnit() }
}
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2020 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.resolve.calls.model
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
interface PostponedResolvedAtomMarker {
val inputTypes: Collection<KotlinTypeMarker>
val outputType: KotlinTypeMarker?
val analyzed: Boolean
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2020 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.resolve.calls.results
import org.jetbrains.kotlin.container.DefaultImplementation
import org.jetbrains.kotlin.container.PlatformSpecificExtension
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
@DefaultImplementation(impl = TypeSpecificityComparator.NONE::class)
interface TypeSpecificityComparator : PlatformSpecificExtension<TypeSpecificityComparator> {
fun isDefinitelyLessSpecific(specific: KotlinTypeMarker, general: KotlinTypeMarker): Boolean
object NONE : TypeSpecificityComparator {
override fun isDefinitelyLessSpecific(specific: KotlinTypeMarker, general: KotlinTypeMarker) = false
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls.tasks;
public enum ExplicitReceiverKind {
EXTENSION_RECEIVER,
DISPATCH_RECEIVER,
NO_EXPLICIT_RECEIVER,
// A very special case.
// In a call 'b.foo(1)' where class 'Foo' has an extension member 'fun B.invoke(Int)' function 'invoke' has two explicit receivers:
// 'b' (as extension receiver) and 'foo' (as dispatch receiver).
BOTH_RECEIVERS;
public boolean isExtensionReceiver() {
return this == EXTENSION_RECEIVER || this == BOTH_RECEIVERS;
}
public boolean isDispatchReceiver() {
return this == DISPATCH_RECEIVER || this == BOTH_RECEIVERS;
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2020 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.resolve.calls.tower
enum class ResolutionCandidateApplicability {
RESOLVED, // call success or has uncompleted inference or in other words possible successful candidate
RESOLVED_WITH_ERROR, // call has error, but it is still successful from resolution perspective
RESOLVED_NEED_PRESERVE_COMPATIBILITY, // call resolved successfully, but using new features that changes resolve
RESOLVED_LOW_PRIORITY,
CONVENTION_ERROR, // missing infix, operator etc
MAY_THROW_RUNTIME_ERROR, // unsafe call or unstable smart cast
RUNTIME_ERROR, // problems with visibility
IMPOSSIBLE_TO_GENERATE, // access to outer class from nested
INAPPLICABLE, // arguments have wrong types
INAPPLICABLE_ARGUMENTS_MAPPING_ERROR, // arguments not mapped to parameters (i.e. different size of arguments and parameters)
INAPPLICABLE_WRONG_RECEIVER, // receiver not matched
HIDDEN, // removed from resolve
RESOLVED_TO_SAM_WITH_VARARG, // migration warning up to 1.5 (when resolve to function with SAM conversion and array without spread as vararg)
}
val ResolutionCandidateApplicability.isSuccess: Boolean
get() = this <= ResolutionCandidateApplicability.RESOLVED_LOW_PRIORITY
@@ -0,0 +1,530 @@
/*
* Copyright 2010-2020 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.types
import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator.commonSuperType
import org.jetbrains.kotlin.types.model.*
import java.util.concurrent.ConcurrentHashMap
abstract class AbstractTypeApproximator(val ctx: TypeSystemInferenceExtensionContext) : TypeSystemInferenceExtensionContext by ctx {
private class ApproximationResult(val type: KotlinTypeMarker?)
private val cacheForIncorporationConfigToSuperDirection = ConcurrentHashMap<KotlinTypeMarker, ApproximationResult>()
private val cacheForIncorporationConfigToSubtypeDirection = ConcurrentHashMap<KotlinTypeMarker, ApproximationResult>()
private val referenceApproximateToSuperType: (SimpleTypeMarker, TypeApproximatorConfiguration, Int) -> KotlinTypeMarker?
get() = this::approximateSimpleToSuperType
private val referenceApproximateToSubType: (SimpleTypeMarker, TypeApproximatorConfiguration, Int) -> KotlinTypeMarker?
get() = this::approximateSimpleToSubType
companion object {
const val CACHE_FOR_INCORPORATION_MAX_SIZE = 500
}
// null means that this input type is the result, i.e. input type not contains not-allowed kind of types
// type <: resultType
fun approximateToSuperType(type: KotlinTypeMarker, conf: TypeApproximatorConfiguration): KotlinTypeMarker? =
approximateToSuperType(type, conf, -type.typeDepth())
// resultType <: type
fun approximateToSubType(type: KotlinTypeMarker, conf: TypeApproximatorConfiguration): KotlinTypeMarker? =
approximateToSubType(type, conf, -type.typeDepth())
fun clearCache() {
cacheForIncorporationConfigToSubtypeDirection.clear()
cacheForIncorporationConfigToSuperDirection.clear()
}
private fun checkExceptionalCases(
type: KotlinTypeMarker, depth: Int, conf: TypeApproximatorConfiguration, toSuper: Boolean
): ApproximationResult? {
return when {
type.isSpecial() ->
null.toApproximationResult()
type.isError() ->
// todo -- fix builtIns. Now builtIns here is DefaultBuiltIns
(if (conf.errorType) null else type.defaultResult(toSuper)).toApproximationResult()
depth > 3 ->
type.defaultResult(toSuper).toApproximationResult()
else -> null
}
}
private fun KotlinTypeMarker?.toApproximationResult(): ApproximationResult = ApproximationResult(this)
private inline fun cachedValue(
type: KotlinTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
approximate: () -> KotlinTypeMarker?
): KotlinTypeMarker? {
// Approximator depends on a configuration, so cache should take it into account
// Here, we cache only types for configuration "from incorporation", which is used most intensively
if (conf !is TypeApproximatorConfiguration.IncorporationConfiguration) return approximate()
val cache = if (toSuper) cacheForIncorporationConfigToSuperDirection else cacheForIncorporationConfigToSubtypeDirection
if (cache.size > CACHE_FOR_INCORPORATION_MAX_SIZE) return approximate()
return cache.getOrPut(type, { approximate().toApproximationResult() }).type
}
private fun approximateToSuperType(type: KotlinTypeMarker, conf: TypeApproximatorConfiguration, depth: Int): KotlinTypeMarker? {
checkExceptionalCases(type, depth, conf, toSuper = true)?.let { return it.type }
return cachedValue(type, conf, toSuper = true) {
approximateTo(
prepareType(type), conf, { upperBound() },
referenceApproximateToSuperType, depth
)
}
}
private fun approximateToSubType(type: KotlinTypeMarker, conf: TypeApproximatorConfiguration, depth: Int): KotlinTypeMarker? {
checkExceptionalCases(type, depth, conf, toSuper = false)?.let { return it.type }
return cachedValue(type, conf, toSuper = false) {
approximateTo(
prepareType(type), conf, { lowerBound() },
referenceApproximateToSubType, depth
)
}
}
// Don't call this method directly, it should be used only in approximateToSuperType/approximateToSubType (use these methods instead)
// This method contains detailed implementation only for type approximation, it doesn't check exceptional cases and doesn't use cache
private fun approximateTo(
type: KotlinTypeMarker,
conf: TypeApproximatorConfiguration,
bound: FlexibleTypeMarker.() -> SimpleTypeMarker,
approximateTo: (SimpleTypeMarker, TypeApproximatorConfiguration, depth: Int) -> KotlinTypeMarker?,
depth: Int
): KotlinTypeMarker? {
when (type) {
is SimpleTypeMarker -> return approximateTo(type, conf, depth)
is FlexibleTypeMarker -> {
if (type.isDynamic()) {
return if (conf.dynamic) null else type.bound()
} else if (type.asRawType() != null) {
return if (conf.rawType) null else type.bound()
}
// TODO: Restore check
// TODO: currently we can lose information about enhancement, should be fixed later
// assert(type is FlexibleTypeImpl || type is FlexibleTypeWithEnhancement) {
// "Unexpected subclass of FlexibleType: ${type::class.java.canonicalName}, type = $type"
// }
if (conf.flexible) {
/**
* Let inputType = L_1..U_1; resultType = L_2..U_2
* We should create resultType such as inputType <: resultType.
* It means that if A <: inputType, then A <: U_1. And, because inputType <: resultType,
* A <: resultType => A <: U_2. I.e. for every type A such A <: U_1, A <: U_2 => U_1 <: U_2.
*
* Similar for L_1 <: L_2: Let B : resultType <: B. L_2 <: B and L_1 <: B.
* I.e. for every type B such as L_2 <: B, L_1 <: B. For example B = L_2.
*/
val lowerBound = type.lowerBound()
val upperBound = type.upperBound()
val lowerResult = approximateTo(lowerBound, conf, depth)
val upperResult = if (type !is RawTypeMarker && lowerBound.typeConstructor() == upperBound.typeConstructor())
lowerResult?.withNullability(upperBound.isMarkedNullable())
else
approximateTo(upperBound, conf, depth)
if (lowerResult == null && upperResult == null) return null
/**
* If C <: L..U then C <: L.
* inputType.lower <: lowerResult => inputType.lower <: lowerResult?.lowerIfFlexible()
* i.e. this type is correct. We use this type, because this type more flexible.
*
* If U_1 <: U_2.lower .. U_2.upper, then we know only that U_1 <: U_2.upper.
*/
return createFlexibleType(
lowerResult?.lowerBoundIfFlexible() ?: lowerBound,
upperResult?.upperBoundIfFlexible() ?: upperBound
)
} else {
return type.bound().let { approximateTo(it, conf, depth) ?: it }
}
}
else -> error("sealed")
}
}
private fun isIntersectionTypeEffectivelyNothing(constructor: IntersectionTypeConstructorMarker): Boolean {
// We consider intersection as Nothing only if one of it's component is a primitive number type
// It's intentional we're not trying to prove population of some type as it was in OI
return constructor.supertypes().any {
!it.isMarkedNullable() && it.isSignedOrUnsignedNumberType()
}
}
private fun approximateIntersectionType(
type: SimpleTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
depth: Int
): KotlinTypeMarker? {
val typeConstructor = type.typeConstructor()
assert(typeConstructor.isIntersection()) {
"Should be intersection type: $type, typeConstructor class: ${typeConstructor::class.java.canonicalName}"
}
assert(typeConstructor.supertypes().isNotEmpty()) {
"Supertypes for intersection type should not be empty: $type"
}
var thereIsApproximation = false
val newTypes = typeConstructor.supertypes().map {
val newType = if (toSuper) approximateToSuperType(it, conf, depth) else approximateToSubType(it, conf, depth)
if (newType != null) {
thereIsApproximation = true
newType
} else it
}
/**
* For case ALLOWED:
* A <: A', B <: B' => A & B <: A' & B'
*
* For other case -- it's impossible to find some type except Nothing as subType for intersection type.
*/
val baseResult = when (conf.intersection) {
TypeApproximatorConfiguration.IntersectionStrategy.ALLOWED -> if (!thereIsApproximation) return null else intersectTypes(newTypes)
TypeApproximatorConfiguration.IntersectionStrategy.TO_FIRST -> if (toSuper) newTypes.first() else return type.defaultResult(toSuper = false)
// commonSupertypeCalculator should handle flexible types correctly
TypeApproximatorConfiguration.IntersectionStrategy.TO_COMMON_SUPERTYPE -> {
if (!toSuper) return type.defaultResult(toSuper = false)
val resultType = commonSuperType(newTypes)
approximateToSuperType(resultType, conf) ?: resultType
}
}
return if (type.isMarkedNullable()) baseResult.withNullability(true) else baseResult
}
private fun approximateCapturedType(
type: CapturedTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
depth: Int
): KotlinTypeMarker? {
val supertypes = type.typeConstructor().supertypes()
val baseSuperType = when (supertypes.size) {
0 -> nullableAnyType() // Let C = in Int, then superType for C and C? is Any?
1 -> supertypes.single()
// Consider the following example:
// A.getA()::class.java, where `getA()` returns some class from Java
// From `::class` we are getting type KClass<Cap<out A!>>, where Cap<out A!> have two supertypes:
// - Any (from declared upper bound of type parameter for KClass)
// - (A..A?) -- from A!, projection type of captured type
// Now, after approximation we were getting type `KClass<out A>`, because { Any & (A..A?) } = A,
// but in old inference type was equal to `KClass<out A!>`.
// Important note that from the point of type system first type is more specific:
// Here, approximation of KClass<Cap<out A!>> is a type KClass<T> such that KClass<Cap<out A!>> <: KClass<out T> =>
// So, the the more specific type for T would be "some non-null (because of declared upper bound type) subtype of A", which is `out A`
// But for now, to reduce differences in behaviour of old and new inference, we'll approximate such types to `KClass<out A!>`
// Once NI will be more stabilized, we'll use more specific type
else -> {
val projection = type.typeConstructorProjection()
if (projection.isStarProjection()) intersectTypes(supertypes.toList())
else projection.getType()
}
}
val baseSubType = type.lowerType() ?: nothingType()
if (conf.capturedType(ctx, type)) {
/**
* Here everything is ok if bounds for this captured type should not be approximated.
* But. If such bounds contains some unauthorized types, then we cannot leave this captured type "as is".
* And we cannot create new capture type, because meaning of new captured type is not clear.
* So, we will just approximate such types
*
* todo handle flexible types
*/
if (approximateToSuperType(baseSuperType, conf, depth) == null && approximateToSubType(baseSubType, conf, depth) == null) {
return null
}
}
val baseResult = if (toSuper) approximateToSuperType(baseSuperType, conf, depth) ?: baseSuperType else approximateToSubType(
baseSubType,
conf,
depth
) ?: baseSubType
// C = in Int, Int <: C => Int? <: C?
// C = out Number, C <: Number => C? <: Number?
return when {
type.isMarkedNullable() -> baseResult.withNullability(true)
type.isProjectionNotNull() -> baseResult.withNullability(false)
else -> baseResult
}
}
private fun approximateSimpleToSuperType(type: SimpleTypeMarker, conf: TypeApproximatorConfiguration, depth: Int) =
approximateTo(type, conf, toSuper = true, depth = depth)
private fun approximateSimpleToSubType(type: SimpleTypeMarker, conf: TypeApproximatorConfiguration, depth: Int) =
approximateTo(type, conf, toSuper = false, depth = depth)
private fun approximateTo(
type: SimpleTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
depth: Int
): KotlinTypeMarker? {
if (type.argumentsCount() != 0) {
return approximateParametrizedType(type, conf, toSuper, depth + 1)
}
val definitelyNotNullType = type.asDefinitelyNotNullType()
if (definitelyNotNullType != null) {
return approximateDefinitelyNotNullType(definitelyNotNullType, conf, toSuper, depth)
}
val typeConstructor = type.typeConstructor()
if (typeConstructor.isCapturedTypeConstructor()) {
val capturedType = type.asCapturedType()
require(capturedType != null) {
// KT-16147
"Type is inconsistent -- somewhere we create type with typeConstructor = $typeConstructor " +
"and class: ${type::class.java.canonicalName}. type.toString() = $type"
}
return approximateCapturedType(capturedType, conf, toSuper, depth)
}
if (typeConstructor.isIntersection()) {
return approximateIntersectionType(type, conf, toSuper, depth)
}
if (typeConstructor is TypeVariableTypeConstructorMarker) {
return if (conf.typeVariable(typeConstructor)) null else type.defaultResult(toSuper)
}
if (typeConstructor.isIntegerLiteralTypeConstructor()) {
return if (conf.integerLiteralType)
typeConstructor.getApproximatedIntegerLiteralType().withNullability(type.isMarkedNullable())
else
null
}
return null // simple classifier type
}
private fun approximateDefinitelyNotNullType(
type: DefinitelyNotNullTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
depth: Int
): KotlinTypeMarker? {
val originalType = type.original()
val approximatedOriginalType =
if (toSuper) approximateToSuperType(originalType, conf, depth) else approximateToSubType(originalType, conf, depth)
return if (conf.definitelyNotNullType) {
approximatedOriginalType?.makeDefinitelyNotNullOrNotNull()
} else {
if (toSuper)
(approximatedOriginalType ?: originalType).withNullability(false)
else
type.defaultResult(toSuper)
}
}
private fun isApproximateDirectionToSuper(effectiveVariance: TypeVariance, toSuper: Boolean) =
when (effectiveVariance) {
TypeVariance.OUT -> toSuper
TypeVariance.IN -> !toSuper
TypeVariance.INV -> throw AssertionError("Incorrect variance $effectiveVariance")
}
private fun approximateParametrizedType(
type: SimpleTypeMarker,
conf: TypeApproximatorConfiguration,
toSuper: Boolean,
depth: Int
): SimpleTypeMarker? {
val typeConstructor = type.typeConstructor()
if (typeConstructor.parametersCount() != type.argumentsCount()) {
return if (conf.errorType) {
createErrorType("Inconsistent type: $type (parameters.size = ${typeConstructor.parametersCount()}, arguments.size = ${type.argumentsCount()})")
} else type.defaultResult(toSuper)
}
val newArguments = arrayOfNulls<TypeArgumentMarker?>(type.argumentsCount())
loop@ for (index in 0 until type.argumentsCount()) {
val parameter = typeConstructor.getParameter(index)
val argument = type.getArgument(index)
if (argument.isStarProjection()) continue
val effectiveVariance = AbstractTypeChecker.effectiveVariance(parameter.getVariance(), argument.getVariance())
val argumentType = newArguments[index]?.getType() ?: argument.getType()
val capturedType = argumentType.lowerBoundIfFlexible().asCapturedType()
val capturedStarProjectionOrNull =
capturedType?.typeConstructorProjection()?.takeIf { it.isStarProjection() }
if (capturedStarProjectionOrNull != null &&
(effectiveVariance == TypeVariance.OUT || effectiveVariance == TypeVariance.INV) &&
toSuper &&
capturedType.typeParameter() == parameter
) {
newArguments[index] = capturedStarProjectionOrNull
continue@loop
}
when (effectiveVariance) {
null -> {
return if (conf.errorType) {
createErrorType(
"Inconsistent type: $type ($index parameter has declared variance: ${parameter.getVariance()}, " +
"but argument variance is ${argument.getVariance()})"
)
} else type.defaultResult(toSuper)
}
TypeVariance.OUT, TypeVariance.IN -> {
if (
conf.intersectionTypesInContravariantPositions &&
effectiveVariance == TypeVariance.IN &&
argumentType.typeConstructor().isIntersection()
) {
val argumentTypeConstructor = argumentType.typeConstructor()
if (argumentTypeConstructor.isIntersection() && isIntersectionTypeEffectivelyNothing(argumentTypeConstructor as IntersectionTypeConstructorMarker)) {
newArguments[index] = createStarProjection(parameter)
continue@loop
}
}
/**
* Out<Foo> <: Out<superType(Foo)>
* Inv<out Foo> <: Inv<out superType(Foo)>
* In<Foo> <: In<subType(Foo)>
* Inv<in Foo> <: Inv<in subType(Foo)>
*/
val approximatedArgument = argumentType.let {
if (isApproximateDirectionToSuper(effectiveVariance, toSuper)) {
approximateToSuperType(it, conf, depth)
} else {
approximateToSubType(it, conf, depth)
}
} ?: continue@loop
if (
conf.intersection != TypeApproximatorConfiguration.IntersectionStrategy.ALLOWED &&
effectiveVariance == TypeVariance.OUT &&
argumentType.typeConstructor().isIntersection()
) {
var shouldReplaceWithStar = false
for (upperBoundIndex in 0 until parameter.upperBoundCount()) {
if (!AbstractTypeChecker.isSubtypeOf(ctx, approximatedArgument, parameter.getUpperBound(upperBoundIndex))) {
shouldReplaceWithStar = true
break
}
}
if (shouldReplaceWithStar) {
newArguments[index] = createStarProjection(parameter)
continue@loop
}
}
if (parameter.getVariance() == TypeVariance.INV) {
newArguments[index] = createTypeArgument(approximatedArgument, effectiveVariance)
} else {
newArguments[index] = approximatedArgument.asTypeArgument()
}
}
TypeVariance.INV -> {
if (!toSuper) {
// Inv<Foo> cannot be approximated to subType
val toSubType = approximateToSubType(argumentType, conf, depth) ?: continue@loop
// Inv<Foo!> is supertype for Inv<Foo?>
if (!AbstractTypeChecker.equalTypes(
this,
argumentType,
toSubType
)
) return type.defaultResult(toSuper)
// also Captured(out Nothing) = Nothing
newArguments[index] = toSubType.asTypeArgument()
continue@loop
}
/**
* Example with non-trivial both type approximations:
* Inv<In<C>> where C = in Int
* Inv<In<C>> <: Inv<out In<Int>>
* Inv<In<C>> <: Inv<in In<Any?>>
*
* So such case is rare and we will chose Inv<out In<Int>> for now.
*
* Note that for case Inv<C> we will chose Inv<in Int>, because it is more informative then Inv<out Any?>.
* May be we should do the same for deeper types, but not now.
*/
if (argumentType.typeConstructor().isCapturedTypeConstructor()) {
val subType = approximateToSubType(argumentType, conf, depth) ?: continue@loop
if (!subType.isTrivialSub()) {
newArguments[index] = createTypeArgument(subType, TypeVariance.IN)
continue@loop
}
}
val approximatedSuperType =
approximateToSuperType(argumentType, conf, depth) ?: continue@loop // null means that this type we can leave as is
if (approximatedSuperType.isTrivialSuper()) {
val approximatedSubType =
approximateToSubType(argumentType, conf, depth) ?: continue@loop // seems like this is never null
if (!approximatedSubType.isTrivialSub()) {
newArguments[index] = createTypeArgument(approximatedSubType, TypeVariance.IN)
continue@loop
}
}
if (AbstractTypeChecker.equalTypes(this, argumentType, approximatedSuperType)) {
newArguments[index] = approximatedSuperType.asTypeArgument()
} else {
newArguments[index] = createTypeArgument(approximatedSuperType, TypeVariance.OUT)
}
}
}
}
if (newArguments.all { it == null }) return null
val newArgumentsList = List(type.argumentsCount()) { index -> newArguments[index] ?: type.getArgument(index) }
return type.replaceArguments(newArgumentsList)
}
private fun KotlinTypeMarker.defaultResult(toSuper: Boolean) = if (toSuper) nullableAnyType() else {
if (this is SimpleTypeMarker && isMarkedNullable()) nullableNothingType() else nothingType()
}
// Any? or Any!
private fun KotlinTypeMarker.isTrivialSuper() = upperBoundIfFlexible().isNullableAny()
// Nothing or Nothing!
private fun KotlinTypeMarker.isTrivialSub() = lowerBoundIfFlexible().isNothing()
}
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2020 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.types
import org.jetbrains.kotlin.types.model.*
open class TypeApproximatorConfiguration {
enum class IntersectionStrategy {
ALLOWED,
TO_FIRST,
TO_COMMON_SUPERTYPE
}
open val flexible: Boolean get() = false // simple flexible types (FlexibleTypeImpl)
open val dynamic: Boolean get() = false // DynamicType
open val rawType: Boolean get() = false // RawTypeImpl
open val errorType: Boolean get() = false
open val integerLiteralType: Boolean = false // IntegerLiteralTypeConstructor
open val definitelyNotNullType: Boolean get() = true
open val intersection: IntersectionStrategy = IntersectionStrategy.TO_COMMON_SUPERTYPE
open val intersectionTypesInContravariantPositions = false
open val typeVariable: (TypeVariableTypeConstructorMarker) -> Boolean = { false }
open fun capturedType(ctx: TypeSystemInferenceExtensionContext, type: CapturedTypeMarker): Boolean =
false // true means that this type we can leave as is
abstract class AllFlexibleSameValue : TypeApproximatorConfiguration() {
abstract val allFlexible: Boolean
override val flexible: Boolean get() = allFlexible
override val dynamic: Boolean get() = allFlexible
override val rawType: Boolean get() = allFlexible
}
object LocalDeclaration : AllFlexibleSameValue() {
override val allFlexible: Boolean get() = true
override val intersection: IntersectionStrategy get() = IntersectionStrategy.ALLOWED
override val errorType: Boolean get() = true
override val integerLiteralType: Boolean get() = true
override val intersectionTypesInContravariantPositions: Boolean get() = true
}
object PublicDeclaration : AllFlexibleSameValue() {
override val allFlexible: Boolean get() = true
override val errorType: Boolean get() = true
override val definitelyNotNullType: Boolean get() = false
override val integerLiteralType: Boolean get() = true
override val intersectionTypesInContravariantPositions: Boolean get() = true
}
abstract class AbstractCapturedTypesApproximation(val approximatedCapturedStatus: CaptureStatus) :
AllFlexibleSameValue() {
override val allFlexible: Boolean get() = true
override val errorType: Boolean get() = true
// i.e. will be approximated only approximatedCapturedStatus captured types
override fun capturedType(ctx: TypeSystemInferenceExtensionContext, type: CapturedTypeMarker): Boolean =
type.captureStatus(ctx) != approximatedCapturedStatus
override val intersection: IntersectionStrategy get() = IntersectionStrategy.ALLOWED
override val typeVariable: (TypeVariableTypeConstructorMarker) -> Boolean get() = { true }
}
object IncorporationConfiguration : AbstractCapturedTypesApproximation(CaptureStatus.FOR_INCORPORATION)
object SubtypeCapturedTypesApproximation : AbstractCapturedTypesApproximation(CaptureStatus.FOR_SUBTYPING)
object InternalTypesApproximation : AbstractCapturedTypesApproximation(CaptureStatus.FROM_EXPRESSION) {
override val integerLiteralType: Boolean get() = true
override val intersectionTypesInContravariantPositions: Boolean get() = true
}
object FinalApproximationAfterResolutionAndInference :
AbstractCapturedTypesApproximation(CaptureStatus.FROM_EXPRESSION) {
override val integerLiteralType: Boolean get() = true
override val intersectionTypesInContravariantPositions: Boolean get() = true
}
object IntegerLiteralsTypesApproximation : AllFlexibleSameValue() {
override val integerLiteralType: Boolean get() = true
override val allFlexible: Boolean get() = true
override val intersection: IntersectionStrategy get() = IntersectionStrategy.ALLOWED
override val typeVariable: (TypeVariableTypeConstructorMarker) -> Boolean get() = { true }
override val errorType: Boolean get() = true
override fun capturedType(ctx: TypeSystemInferenceExtensionContext, type: CapturedTypeMarker): Boolean = true
}
}