diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt index 9ea9ffaee5d..dfc234a4ee1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/types/ConeTypeContext.kt @@ -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 diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt index 06091d31578..87003db98a6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/DiagnosticReporterByTrackingStrategy.kt @@ -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()?.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)) } } } diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ConstraintInjector.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ConstraintInjector.kt index d530d1ba2ad..b7ff4875b42 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ConstraintInjector.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ConstraintInjector.kt @@ -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 diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt index 6a7d9020ee2..30020e99d9e 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/KotlinConstraintSystemCompleter.kt @@ -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 + ) { + 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? { + 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 + } } \ No newline at end of file diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt index 66ad5bdf2b0..0738950bee9 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/ResultTypeResolver.kt @@ -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 diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/MutableConstraintStorage.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/MutableConstraintStorage.kt index 570c289c391..9e9f9e8fdc4 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/MutableConstraintStorage.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/model/MutableConstraintStorage.kt @@ -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 { - 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): MutableList { + 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>): Boolean { diff --git a/compiler/testData/diagnostics/tests/generics/kt30590.kt b/compiler/testData/diagnostics/tests/generics/kt30590.kt index f97e5a8fc32..b66ca971e1d 100644 --- a/compiler/testData/diagnostics/tests/generics/kt30590.kt +++ b/compiler/testData/diagnostics/tests/generics/kt30590.kt @@ -1,11 +1,9 @@ // !WITH_NEW_INFERENCE // !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE -// !LANGUAGE: +NewInference -// Issue: KT-30590 +// NI_EXPECTED_FILE interface A fun emptyStrangeMap(): Map = TODO() fun test7() : Map = emptyStrangeMap() -fun test() = emptyStrangeMap() - +fun test() = emptyStrangeMap() diff --git a/compiler/testData/diagnostics/tests/generics/kt30590.ni.txt b/compiler/testData/diagnostics/tests/generics/kt30590.ni.txt new file mode 100644 index 00000000000..a77bc0861ab --- /dev/null +++ b/compiler/testData/diagnostics/tests/generics/kt30590.ni.txt @@ -0,0 +1,11 @@ +package + +public fun emptyStrangeMap(): kotlin.collections.Map +public fun test(): kotlin.collections.Map +public fun test7(): kotlin.collections.Map + +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 +} diff --git a/compiler/testData/diagnostics/tests/generics/kt30590.txt b/compiler/testData/diagnostics/tests/generics/kt30590.txt index 635c2d832f1..7da0b4336a1 100644 --- a/compiler/testData/diagnostics/tests/generics/kt30590.txt +++ b/compiler/testData/diagnostics/tests/generics/kt30590.txt @@ -1,6 +1,7 @@ package public fun emptyStrangeMap(): kotlin.collections.Map +public fun test(): [ERROR : Error function type] public fun test7(): kotlin.collections.Map public interface A { diff --git a/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.kt b/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.kt index b065052ca44..6bde24e0526 100644 --- a/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.kt +++ b/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.kt @@ -2,8 +2,12 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER fun id2(x: K, s: String): K = x +fun ret(s: String): K = TODO() fun test() { - id2(unresolved, "foo") - id2(unresolved, 42) + id2(unresolved, "foo") + id2(unresolved, 42) + + ret("foo") + ret(42) } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.txt b/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.txt index 7acd852fee9..42f6e5688aa 100644 --- a/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.txt +++ b/compiler/testData/diagnostics/tests/inference/reportingImprovements/inferTypeFromUnresolvedArgument.txt @@ -1,4 +1,5 @@ package public fun id2(/*0*/ x: K, /*1*/ s: kotlin.String): K +public fun ret(/*0*/ s: kotlin.String): K public fun test(): kotlin.Unit diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/checker/ClassicTypeSystemContext.kt b/core/descriptors/src/org/jetbrains/kotlin/types/checker/ClassicTypeSystemContext.kt index dd116b37f55..5bbd893ee37 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/checker/ClassicTypeSystemContext.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/checker/ClassicTypeSystemContext.kt @@ -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 diff --git a/core/type-system/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt b/core/type-system/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt index 32a83190c21..4c68611eefb 100644 --- a/core/type-system/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt +++ b/core/type-system/src/org/jetbrains/kotlin/types/model/TypeSystemContext.kt @@ -145,6 +145,7 @@ interface TypeSystemContext : TypeSystemOptimizationContext { fun KotlinTypeMarker.asFlexibleType(): FlexibleTypeMarker? fun KotlinTypeMarker.isError(): Boolean + fun KotlinTypeMarker.isUninferredParameter(): Boolean fun FlexibleTypeMarker.asDynamicType(): DynamicTypeMarker?