[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
@@ -63,6 +63,11 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext {
return this is ConeClassErrorType || this is ConeKotlinErrorType || this.typeConstructor() is ErrorTypeConstructor
}
override fun KotlinTypeMarker.isUninferredParameter(): Boolean {
assert(this is ConeKotlinType)
return false // TODO
}
override fun FlexibleTypeMarker.asDynamicType(): DynamicTypeMarker? {
assert(this is ConeKotlinType)
return null // TODO
@@ -229,6 +229,7 @@ class DiagnosticReporterByTrackingStrategy(
trace.report(UPPER_BOUND_VIOLATED.on(typeArgumentReference, constraintError.upperKotlinType, constraintError.lowerKotlinType))
}
}
CapturedTypeFromSubtyping::class.java -> {
val capturedError = diagnostic as CapturedTypeFromSubtyping
val position = capturedError.position
@@ -248,6 +249,9 @@ class DiagnosticReporterByTrackingStrategy(
}
NotEnoughInformationForTypeParameter::class.java -> {
if (allDiagnostics.any {it is ConstrainingTypeIsError || it is NewConstraintError || it is WrongCountOfTypeArguments})
return
val error = diagnostic as NotEnoughInformationForTypeParameter
val call = error.resolvedAtom.atom?.safeAs<PSIKotlinCall>()?.psiCall ?: call
val expression = call.calleeExpression ?: return
@@ -256,7 +260,7 @@ class DiagnosticReporterByTrackingStrategy(
is TypeVariableForLambdaReturnType -> "return type of lambda"
else -> error("Unsupported type variable: $typeVariable")
}
trace.report(NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER.on(expression, typeVariableName))
trace.reportDiagnosticOnce(NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER.on(expression, typeVariableName))
}
}
}
@@ -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 {
+2 -4
View File
@@ -1,11 +1,9 @@
// !WITH_NEW_INFERENCE
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE
// !LANGUAGE: +NewInference
// Issue: KT-30590
// NI_EXPECTED_FILE
interface A
fun <T: A, R: T> emptyStrangeMap(): Map<T, R> = TODO()
fun test7() : Map<A, A> = emptyStrangeMap()
fun test() = emptyStrangeMap()
fun test() = <!NI;NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER, OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>emptyStrangeMap<!>()
@@ -0,0 +1,11 @@
package
public fun </*0*/ T : A, /*1*/ R : T> emptyStrangeMap(): kotlin.collections.Map<T, R>
public fun test(): kotlin.collections.Map<???, ???>
public fun test7(): kotlin.collections.Map<A, A>
public interface A {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -1,6 +1,7 @@
package
public fun </*0*/ T : A, /*1*/ R : T> emptyStrangeMap(): kotlin.collections.Map<T, R>
public fun test(): [ERROR : Error function type]
public fun test7(): kotlin.collections.Map<A, A>
public interface A {
@@ -2,8 +2,12 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun <K> id2(x: K, s: String): K = x
fun <K> ret(s: String): K = TODO()
fun test() {
<!NI;IMPLICIT_NOTHING_AS_TYPE_PARAMETER, NI;NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>id2<!>(<!UNRESOLVED_REFERENCE!>unresolved<!>, "foo")
<!NI;UNREACHABLE_CODE!><!NI;IMPLICIT_NOTHING_AS_TYPE_PARAMETER, NI;NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>id2<!>(<!UNRESOLVED_REFERENCE!>unresolved<!>, <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!>)<!>
id2(<!UNRESOLVED_REFERENCE!>unresolved<!>, "foo")
id2(<!UNRESOLVED_REFERENCE!>unresolved<!>, <!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!>)
<!NI;NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER, OI;TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>ret<!>("foo")
<!OI;TYPE_INFERENCE_PARAMETER_CONSTRAINT_ERROR!>ret<!>(<!CONSTANT_EXPECTED_TYPE_MISMATCH!>42<!>)
}
@@ -1,4 +1,5 @@
package
public fun </*0*/ K> id2(/*0*/ x: K, /*1*/ s: kotlin.String): K
public fun </*0*/ K> ret(/*0*/ s: kotlin.String): K
public fun test(): kotlin.Unit
@@ -49,6 +49,11 @@ interface ClassicTypeSystemContext : TypeSystemInferenceExtensionContext {
return this.isError
}
override fun KotlinTypeMarker.isUninferredParameter(): Boolean {
require(this is KotlinType, this::errorMessage)
return ErrorUtils.isUninferredParameter(this)
}
override fun SimpleTypeMarker.isStubType(): Boolean {
require(this is SimpleType, this::errorMessage)
return this is StubType
@@ -145,6 +145,7 @@ interface TypeSystemContext : TypeSystemOptimizationContext {
fun KotlinTypeMarker.asFlexibleType(): FlexibleTypeMarker?
fun KotlinTypeMarker.isError(): Boolean
fun KotlinTypeMarker.isUninferredParameter(): Boolean
fun FlexibleTypeMarker.asDynamicType(): DynamicTypeMarker?