[NI] Improvements for "not enough information" diagnostic
#KT-30590 Fixed
This commit is contained in:
@@ -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
|
||||
|
||||
+5
-1
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
+53
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -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
|
||||
|
||||
+46
-14
@@ -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
@@ -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 {
|
||||
|
||||
+6
-2
@@ -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
@@ -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?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user