[NI] Improvements for "not enough information" diagnostic

#KT-30590 Fixed
This commit is contained in:
Mikhail Zarechenskiy
2019-04-23 00:45:29 +03:00
parent 09cc2ae27f
commit ff0993153c
13 changed files with 142 additions and 29 deletions
@@ -166,6 +166,11 @@ class ConstraintInjector(val constraintIncorporator: ConstraintIncorporator, val
?: error("Should by type variableConstructor: $typeVariableConstructor. ${c.allTypeVariables.values}")
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
@@ -6,8 +6,10 @@
package org.jetbrains.kotlin.resolve.calls.inference.components
import org.jetbrains.kotlin.resolve.calls.inference.model.NotEnoughInformationForTypeParameter
import org.jetbrains.kotlin.resolve.calls.inference.model.TypeVariableFromCallableDescriptor
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
@@ -84,11 +86,11 @@ class KotlinConstraintSystemCompleter(
if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) {
val variableWithConstraints = c.notFixedTypeVariables.getValue(variableForFixation.variable)
fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives)
if (variableForFixation.hasProperConstraint)
fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives)
else
processVariableWhenNotEnoughInformation(c, variableWithConstraints, topLevelAtoms)
if (!variableForFixation.hasProperConstraint) {
c.addError(NotEnoughInformationForTypeParameter(variableWithConstraints.typeVariable, topLevelAtoms.first()))
}
continue
}
@@ -218,4 +220,51 @@ class KotlinConstraintSystemCompleter(
val resultType = resultTypeResolver.findResultType(c, variableWithConstraints, direction)
c.fixVariable(variableWithConstraints.typeVariable, resultType)
}
private fun processVariableWhenNotEnoughInformation(
c: Context,
variableWithConstraints: VariableWithConstraints,
topLevelAtoms: List<ResolvedAtom>
) {
val typeVariable = variableWithConstraints.typeVariable
val resolvedAtom = findResolvedAtomBy(typeVariable, topLevelAtoms) ?: topLevelAtoms.firstOrNull()
if (resolvedAtom != null) {
c.addError(NotEnoughInformationForTypeParameter(typeVariable, resolvedAtom))
}
val resultErrorType = if (typeVariable is TypeVariableFromCallableDescriptor)
ErrorUtils.createUninferredParameterType(typeVariable.originalTypeParameter)
else
ErrorUtils.createErrorType("Cannot infer type variable $typeVariable")
c.fixVariable(typeVariable, resultErrorType)
}
private fun findResolvedAtomBy(typeVariable: TypeVariableMarker, topLevelAtoms: List<ResolvedAtom>): ResolvedAtom? {
fun ResolvedAtom.check(): ResolvedAtom? {
val suitableCall = when (this) {
is ResolvedCallAtom -> typeVariable in substitutor.freshVariables
is ResolvedCallableReferenceAtom -> candidate?.freshSubstitutor?.freshVariables?.let { typeVariable in it } ?: false
is ResolvedLambdaAtom -> typeVariable == typeVariableForLambdaReturnType
else -> false
}
if (suitableCall) {
return this
}
subResolvedAtoms.forEach { subResolvedAtom ->
subResolvedAtom.check()?.let { result -> return@check result }
}
return null
}
for (topLevelAtom in topLevelAtoms) {
topLevelAtom.check()?.let { return it }
}
return null
}
}
@@ -18,10 +18,7 @@ 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.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.inference.model.checkConstraint
import org.jetbrains.kotlin.resolve.calls.inference.model.*
import org.jetbrains.kotlin.resolve.constants.IntegerLiteralTypeConstructor
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
@@ -5,12 +5,9 @@
package org.jetbrains.kotlin.resolve.calls.inference.model
import org.jetbrains.kotlin.resolve.calls.inference.components.NewTypeSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.trimToSize
import org.jetbrains.kotlin.resolve.calls.model.KotlinCallDiagnostic
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.TypeConstructor
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
@@ -38,16 +35,22 @@ class MutableVariableWithConstraints(
fun addConstraint(constraint: Constraint): Constraint? {
val previousConstraintWithSameType = constraints.filter { it.typeHashCode == constraint.typeHashCode && it.type == constraint.type }
if (previousConstraintWithSameType.any { newConstraintIsUseless(it.kind, constraint.kind) }) {
if (previousConstraintWithSameType.any { previous -> newConstraintIsUseless(previous, constraint) })
return null
val addAsEqualityConstraint = previousConstraintWithSameType.any { previous ->
when (previous.kind) {
ConstraintKind.LOWER -> constraint.kind.isUpper()
ConstraintKind.UPPER -> constraint.kind.isLower()
ConstraintKind.EQUALITY -> true
}
}
val actualConstraint = if (previousConstraintWithSameType.isNotEmpty()) {
// i.e. previous is LOWER and new is UPPER or opposite situation
val actualConstraint = if (addAsEqualityConstraint)
Constraint(ConstraintKind.EQUALITY, constraint.type, constraint.position, constraint.typeHashCode)
} else {
else
constraint
}
mutableConstraints.add(actualConstraint)
simplifiedConstraints = null
return actualConstraint
@@ -66,18 +69,47 @@ class MutableVariableWithConstraints(
simplifiedConstraints = null
}
private fun newConstraintIsUseless(oldKind: ConstraintKind, newKind: ConstraintKind) =
when (oldKind) {
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 -> newKind == ConstraintKind.LOWER
ConstraintKind.UPPER -> newKind == ConstraintKind.UPPER
ConstraintKind.LOWER -> new.kind.isLower()
ConstraintKind.UPPER -> new.kind.isUpper()
}
}
private fun simplifyConstraints(): List<Constraint> {
val equalityConstraints = mutableConstraints
val distinctConstraints = removeDuplicatesFromDeclaredUpperBoundConstraints(mutableConstraints)
val equalityConstraints = distinctConstraints
.filter { it.kind == ConstraintKind.EQUALITY }
.groupBy { it.typeHashCode }
return mutableConstraints.filter { isUsefulConstraint(it, equalityConstraints) }
return distinctConstraints.filter { isUsefulConstraint(it, equalityConstraints) }
}
private fun removeDuplicatesFromDeclaredUpperBoundConstraints(constraints: List<Constraint>): MutableList<Constraint> {
val currentConstraints = constraints.toMutableList()
val iterator = currentConstraints.iterator()
while (iterator.hasNext()) {
val potentialDuplicate = iterator.next()
if (potentialDuplicate.position.from !is DeclaredUpperBoundConstraintPosition) continue
val hasDuplicate = currentConstraints.any { other ->
potentialDuplicate !== other &&
potentialDuplicate.typeHashCode == other.typeHashCode &&
potentialDuplicate.type == other.type &&
potentialDuplicate.kind == other.kind
}
if (hasDuplicate)
iterator.remove()
}
return currentConstraints
}
private fun isUsefulConstraint(constraint: Constraint, equalityConstraints: Map<Int, List<Constraint>>): Boolean {