Introduce warnings reporting by missed constraints because of incorrect optimization in the constraints processor

This commit is contained in:
Victor Petukhov
2021-04-26 20:14:21 +03:00
parent e110b49cab
commit 7c62e9aecd
41 changed files with 535 additions and 46 deletions
@@ -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 {
@@ -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)
}
}
}
@@ -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)
@@ -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)
@@ -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()
@@ -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