Introduce warnings reporting by missed constraints because of incorrect optimization in the constraints processor
This commit is contained in:
+3
@@ -8,6 +8,7 @@ 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.resolve.calls.inference.model.ConstraintSystemError
|
||||
import org.jetbrains.kotlin.types.model.*
|
||||
|
||||
interface ConstraintSystemOperation {
|
||||
@@ -45,6 +46,8 @@ interface ConstraintSystemOperation {
|
||||
fun getProperSuperTypeConstructors(type: KotlinTypeMarker): List<TypeConstructorMarker>
|
||||
|
||||
fun addOtherSystem(otherSystem: ConstraintStorage)
|
||||
|
||||
val errors: List<ConstraintSystemError>
|
||||
}
|
||||
|
||||
interface ConstraintSystemBuilder : ConstraintSystemOperation {
|
||||
|
||||
+80
-27
@@ -34,6 +34,11 @@ class ConstraintInjector(
|
||||
|
||||
fun addInitialConstraint(initialConstraint: InitialConstraint)
|
||||
fun addError(error: ConstraintSystemError)
|
||||
|
||||
fun addMissedConstraints(
|
||||
position: IncorporationConstraintPosition,
|
||||
constraints: MutableList<Pair<TypeVariableMarker, Constraint>>
|
||||
)
|
||||
}
|
||||
|
||||
fun addInitialSubtypeConstraint(c: Context, lowerType: KotlinTypeMarker, upperType: KotlinTypeMarker, position: ConstraintPosition) {
|
||||
@@ -85,7 +90,12 @@ class ConstraintInjector(
|
||||
typeCheckerContext.setConstrainingTypesToPrintDebugInfo(lowerType, upperType)
|
||||
typeCheckerContext.runIsSubtypeOf(lowerType, upperType)
|
||||
|
||||
processConstraints(c, typeCheckerContext)
|
||||
// Missed constraints are constraints which we skipped in the constraints processor by mistake (incorrect optimization)
|
||||
val missedConstraints = processConstraints(c, typeCheckerContext)
|
||||
|
||||
if (missedConstraints != null) {
|
||||
c.addMissedConstraints(typeCheckerContext.position, missedConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addEqualityConstraintAndIncorporateIt(
|
||||
@@ -97,41 +107,84 @@ class ConstraintInjector(
|
||||
typeCheckerContext.setConstrainingTypesToPrintDebugInfo(typeVariable, equalType)
|
||||
typeCheckerContext.addEqualityConstraint(typeVariable.typeConstructor(c), equalType)
|
||||
|
||||
processConstraints(c, typeCheckerContext)
|
||||
// Missed constraints are constraints which we skipped in the constraints processor by mistake (incorrect optimization)
|
||||
val missedConstraints = processConstraints(c, typeCheckerContext)
|
||||
|
||||
if (missedConstraints != null) {
|
||||
c.addMissedConstraints(typeCheckerContext.position, missedConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processConstraints(c: Context, typeCheckerContext: TypeCheckerContext) {
|
||||
fun processMissedConstraints(
|
||||
c: Context,
|
||||
position: IncorporationConstraintPosition,
|
||||
missedConstraints: List<Pair<TypeVariableMarker, Constraint>>
|
||||
) {
|
||||
val properConstraintsProcessingEnabled =
|
||||
languageVersionSettings.supportsFeature(LanguageFeature.ProperTypeInferenceConstraintsProcessing)
|
||||
|
||||
// If proper constraints processing is enabled, then we don't have missed constraints
|
||||
if (properConstraintsProcessingEnabled) return
|
||||
|
||||
val typeCheckerContext = TypeCheckerContext(c, position)
|
||||
for ((variable, constraint) in missedConstraints) {
|
||||
typeCheckerContext.addPossibleNewConstraint(variable, constraint)
|
||||
}
|
||||
processConstraints(c, typeCheckerContext, skipProperEqualityConstraints = false)
|
||||
}
|
||||
|
||||
private fun processConstraints(
|
||||
c: Context,
|
||||
typeCheckerContext: TypeCheckerContext,
|
||||
skipProperEqualityConstraints: Boolean = true
|
||||
): MutableList<Pair<TypeVariableMarker, Constraint>>? {
|
||||
val properConstraintsProcessingEnabled =
|
||||
languageVersionSettings.supportsFeature(LanguageFeature.ProperTypeInferenceConstraintsProcessing)
|
||||
|
||||
while (typeCheckerContext.hasConstraintsToProcess()) {
|
||||
for ((typeVariable, constraint) in typeCheckerContext.extractAllConstraints()!!) {
|
||||
if (c.shouldWeSkipConstraint(typeVariable, constraint)) continue
|
||||
processGivenConstraints(c, typeCheckerContext, typeCheckerContext.extractAllConstraints()!!)
|
||||
|
||||
val constraints =
|
||||
c.notFixedTypeVariables[typeVariable.freshTypeConstructor(c)] ?: typeCheckerContext.fixedTypeVariable(typeVariable)
|
||||
val contextOps = c as? ConstraintSystemOperation
|
||||
|
||||
// it is important, that we add constraint here(not inside TypeCheckerContext), because inside incorporation we read constraints
|
||||
val (addedOrNonRedundantExistedConstraint, wasAdded) = constraints.addConstraint(constraint)
|
||||
val positionFrom = constraint.position.from
|
||||
val constraintToIncorporate = when {
|
||||
wasAdded && !constraint.isNullabilityConstraint -> addedOrNonRedundantExistedConstraint
|
||||
positionFrom is FixVariableConstraintPosition<*> && positionFrom.variable == typeVariable && constraint.kind == EQUALITY ->
|
||||
addedOrNonRedundantExistedConstraint
|
||||
else -> null
|
||||
}
|
||||
val useIncorrectOptimization = skipProperEqualityConstraints && !properConstraintsProcessingEnabled
|
||||
|
||||
if (constraintToIncorporate != null) {
|
||||
constraintIncorporator.incorporate(typeCheckerContext, typeVariable, constraintToIncorporate)
|
||||
if (!useIncorrectOptimization) continue
|
||||
|
||||
// Optimization below is wrong and it's going to be removed after finished the corresponding deprecation cycle
|
||||
val hasProperEqualityConstraintForEachVariable = contextOps != null && c.notFixedTypeVariables.all { typeVariable ->
|
||||
typeVariable.value.constraints.any { constraint ->
|
||||
constraint.kind == EQUALITY && contextOps.isProperType(constraint.type)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (hasProperEqualityConstraintForEachVariable) return typeCheckerContext.extractAllConstraints()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processGivenConstraints(
|
||||
c: Context,
|
||||
typeCheckerContext: TypeCheckerContext,
|
||||
constraintsToProcess: MutableList<Pair<TypeVariableMarker, Constraint>>
|
||||
) {
|
||||
for ((typeVariable, constraint) in constraintsToProcess) {
|
||||
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
|
||||
val (addedOrNonRedundantExistedConstraint, wasAdded) = constraints.addConstraint(constraint)
|
||||
val positionFrom = constraint.position.from
|
||||
val constraintToIncorporate = when {
|
||||
wasAdded && !constraint.isNullabilityConstraint -> addedOrNonRedundantExistedConstraint
|
||||
positionFrom is FixVariableConstraintPosition<*> && positionFrom.variable == typeVariable && constraint.kind == EQUALITY ->
|
||||
addedOrNonRedundantExistedConstraint
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (constraintToIncorporate != null) {
|
||||
constraintIncorporator.incorporate(typeCheckerContext, typeVariable, constraintToIncorporate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -83,7 +83,8 @@ sealed class ConstraintSystemError(val applicability: CandidateApplicability)
|
||||
class NewConstraintError(
|
||||
val lowerType: KotlinTypeMarker,
|
||||
val upperType: KotlinTypeMarker,
|
||||
val position: IncorporationConstraintPosition
|
||||
val position: IncorporationConstraintPosition,
|
||||
val isWarning: Boolean = false
|
||||
) : ConstraintSystemError(if (position.from is ReceiverConstraintPosition<*>) INAPPLICABLE_WRONG_RECEIVER else INAPPLICABLE)
|
||||
|
||||
class CapturedTypeFromSubtyping(
|
||||
@@ -109,3 +110,5 @@ object LowerPriorityToPreserveCompatibility : ConstraintSystemError(RESOLVED_NEE
|
||||
|
||||
fun Constraint.isExpectedTypePosition() =
|
||||
position.from is ExpectedTypeConstraintPosition<*> || position.from is DelegatedPropertyConstraintPosition<*>
|
||||
|
||||
fun NewConstraintError.transformToWarning() = NewConstraintError(lowerType, upperType, position, isWarning = true)
|
||||
|
||||
+5
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.types.model.*
|
||||
interface ConstraintStorage {
|
||||
val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker>
|
||||
val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints>
|
||||
val missedConstraints: List<Pair<IncorporationConstraintPosition, List<Pair<TypeVariableMarker, Constraint>>>>
|
||||
val initialConstraints: List<InitialConstraint>
|
||||
val maxTypeDepthFromInitialConstraints: Int
|
||||
val errors: List<ConstraintSystemError>
|
||||
@@ -45,6 +46,7 @@ interface ConstraintStorage {
|
||||
object Empty : ConstraintStorage {
|
||||
override val allTypeVariables: Map<TypeConstructorMarker, TypeVariableMarker> get() = emptyMap()
|
||||
override val notFixedTypeVariables: Map<TypeConstructorMarker, VariableWithConstraints> get() = emptyMap()
|
||||
override val missedConstraints: List<Pair<IncorporationConstraintPosition, List<Pair<TypeVariableMarker, Constraint>>>> get() = emptyList()
|
||||
override val initialConstraints: List<InitialConstraint> get() = emptyList()
|
||||
override val maxTypeDepthFromInitialConstraints: Int get() = 1
|
||||
override val errors: List<ConstraintSystemError> get() = emptyList()
|
||||
@@ -143,3 +145,6 @@ fun checkConstraint(
|
||||
ConstraintKind.UPPER -> typeChecker.isSubtypeOf(context, resultType, constraintType)
|
||||
}
|
||||
}
|
||||
|
||||
fun Constraint.replaceType(newType: KotlinTypeMarker) =
|
||||
Constraint(kind, newType, position, typeHashCode, derivedFrom, isNullabilityConstraint, inputTypePositionBeforeIncorporation)
|
||||
|
||||
+7
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.resolve.calls.inference.model
|
||||
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.components.AbstractTypeCheckerContextForConstraintSystem
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.components.ConstraintSystemUtilContext
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
|
||||
import org.jetbrains.kotlin.types.model.*
|
||||
@@ -47,6 +48,8 @@ class MutableVariableWithConstraints private constructor(
|
||||
|
||||
private var simplifiedConstraints: SmartList<Constraint>? = mutableConstraints
|
||||
|
||||
val rawConstraintsCount get() = mutableConstraints.size
|
||||
|
||||
// return new actual constraint, if this constraint is new, otherwise return already existed not redundant constraint
|
||||
// the second element of pair is a flag whether a constraint was added in fact
|
||||
fun addConstraint(constraint: Constraint): Pair<Constraint, Boolean> {
|
||||
@@ -104,8 +107,8 @@ class MutableVariableWithConstraints private constructor(
|
||||
|
||||
// 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)
|
||||
internal fun removeLastConstraints(sinceIndex: Int) {
|
||||
mutableConstraints.trimToSize(sinceIndex)
|
||||
if (simplifiedConstraints !== mutableConstraints) {
|
||||
simplifiedConstraints = null
|
||||
}
|
||||
@@ -212,6 +215,8 @@ class MutableVariableWithConstraints private constructor(
|
||||
internal class MutableConstraintStorage : ConstraintStorage {
|
||||
override val allTypeVariables: MutableMap<TypeConstructorMarker, TypeVariableMarker> = LinkedHashMap()
|
||||
override val notFixedTypeVariables: MutableMap<TypeConstructorMarker, MutableVariableWithConstraints> = LinkedHashMap()
|
||||
override val missedConstraints: MutableList<Pair<IncorporationConstraintPosition, MutableList<Pair<TypeVariableMarker, Constraint>>>> =
|
||||
SmartList()
|
||||
override val initialConstraints: MutableList<InitialConstraint> = SmartList()
|
||||
override var maxTypeDepthFromInitialConstraints: Int = 1
|
||||
override val errors: MutableList<ConstraintSystemError> = SmartList()
|
||||
|
||||
+58
-3
@@ -91,6 +91,13 @@ class NewConstraintSystemImpl(
|
||||
return storage
|
||||
}
|
||||
|
||||
override fun addMissedConstraints(
|
||||
position: IncorporationConstraintPosition,
|
||||
constraints: MutableList<Pair<TypeVariableMarker, Constraint>>
|
||||
) {
|
||||
storage.missedConstraints.add(position to constraints)
|
||||
}
|
||||
|
||||
override fun asConstraintSystemCompleterContext() = apply { checkState(State.BUILDING) }
|
||||
|
||||
override fun asPostponedArgumentsAnalyzerContext() = apply { checkState(State.BUILDING) }
|
||||
@@ -187,6 +194,8 @@ class NewConstraintSystemImpl(
|
||||
val beforeErrorsCount = storage.errors.size
|
||||
val beforeMaxTypeDepthFromInitialConstraints = storage.maxTypeDepthFromInitialConstraints
|
||||
val beforeTypeVariablesTransactionSize = typeVariablesTransaction.size
|
||||
val beforeMissedConstraintsCount = storage.missedConstraints.size
|
||||
val beforeConstraintCountByVariables = storage.notFixedTypeVariables.mapValues { it.value.rawConstraintsCount }
|
||||
|
||||
state = State.TRANSACTION
|
||||
// typeVariablesTransaction is clear
|
||||
@@ -201,13 +210,16 @@ class NewConstraintSystemImpl(
|
||||
}
|
||||
storage.maxTypeDepthFromInitialConstraints = beforeMaxTypeDepthFromInitialConstraints
|
||||
storage.errors.trimToSize(beforeErrorsCount)
|
||||
storage.missedConstraints.trimToSize(beforeMissedConstraintsCount)
|
||||
|
||||
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)
|
||||
val sinceIndexToRemoveConstraints =
|
||||
beforeConstraintCountByVariables[variableWithConstraint.typeVariable.freshTypeConstructor()]
|
||||
if (sinceIndexToRemoveConstraints != null) {
|
||||
variableWithConstraint.removeLastConstraints(sinceIndexToRemoveConstraints)
|
||||
}
|
||||
}
|
||||
|
||||
addedInitialConstraints.clear() // remove constraint from storage.initialConstraints
|
||||
@@ -334,6 +346,13 @@ class NewConstraintSystemImpl(
|
||||
|
||||
constraintInjector.addInitialEqualityConstraint(this@NewConstraintSystemImpl, variable.defaultType(), resultType, position)
|
||||
|
||||
/*
|
||||
* Checking missed constraint can introduce new type mismatch warnings.
|
||||
* It's needed to deprecate green code which works only due to incorrect optimization in the constraint injector.
|
||||
* TODO: remove this code (and `substituteMissedConstraints`) with removing `ProperTypeInferenceConstraintsProcessing` feature
|
||||
*/
|
||||
checkMissedConstraints()
|
||||
|
||||
val freshTypeConstructor = variable.freshTypeConstructor()
|
||||
val variableWithConstraints = notFixedTypeVariables.remove(freshTypeConstructor)
|
||||
|
||||
@@ -343,11 +362,47 @@ class NewConstraintSystemImpl(
|
||||
|
||||
storage.fixedTypeVariables[freshTypeConstructor] = resultType
|
||||
|
||||
// Substitute freshly fixed type variable into missed constraints
|
||||
substituteMissedConstraints()
|
||||
|
||||
postponeOnlyInputTypesCheck(variableWithConstraints, resultType)
|
||||
|
||||
doPostponedComputationsIfAllVariablesAreFixed()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun checkMissedConstraints() {
|
||||
val constraintSystem = this@NewConstraintSystemImpl
|
||||
val errorsByMissedConstraints = buildList {
|
||||
runTransaction {
|
||||
for ((position, constraints) in storage.missedConstraints) {
|
||||
val fixedVariableConstraints =
|
||||
constraints.filter { (typeVariable, _) -> typeVariable.freshTypeConstructor() in notFixedTypeVariables }
|
||||
constraintInjector.processMissedConstraints(constraintSystem, position, fixedVariableConstraints)
|
||||
}
|
||||
errors.filterIsInstance<NewConstraintError>().forEach(::add)
|
||||
false
|
||||
}
|
||||
}
|
||||
val constraintErrors = constraintSystem.errors.filterIsInstance<NewConstraintError>()
|
||||
// Don't report warning if an error on the same call has already been reported
|
||||
if (constraintErrors.isEmpty() || constraintErrors.all { it.isWarning }) {
|
||||
errorsByMissedConstraints.forEach {
|
||||
constraintSystem.addError(it.transformToWarning())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun substituteMissedConstraints() {
|
||||
val substitutor = buildCurrentSubstitutor()
|
||||
for ((_, constraints) in storage.missedConstraints) {
|
||||
for ((index, variableWithConstraint) in constraints.withIndex()) {
|
||||
val (typeVariable, constraint) = variableWithConstraint
|
||||
constraints[index] = typeVariable to constraint.replaceType(substitutor.safeSubstitute(constraint.type))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConstraintSystemUtilContext.postponeOnlyInputTypesCheck(
|
||||
variableWithConstraints: MutableVariableWithConstraints?,
|
||||
resultType: KotlinTypeMarker
|
||||
|
||||
Reference in New Issue
Block a user