[NI] Discriminate constraints with Nothing(?) lower bounds
This commit is contained in:
+13
-15
@@ -69,27 +69,27 @@ class KotlinConstraintSystemCompleter(
|
|||||||
|
|
||||||
val allTypeVariables = getOrderedAllTypeVariables(c, collectVariablesFromContext, topLevelAtoms)
|
val allTypeVariables = getOrderedAllTypeVariables(c, collectVariablesFromContext, topLevelAtoms)
|
||||||
val postponedKtPrimitives = getOrderedNotAnalyzedPostponedArguments(topLevelAtoms)
|
val postponedKtPrimitives = getOrderedNotAnalyzedPostponedArguments(topLevelAtoms)
|
||||||
val variableForFixation = variableFixationFinder.findFirstVariableForFixation(
|
val variableForFixation =
|
||||||
c, allTypeVariables, postponedKtPrimitives, completionMode, topLevelType
|
variableFixationFinder.findFirstVariableForFixation(
|
||||||
)
|
c, allTypeVariables, postponedKtPrimitives, completionMode, topLevelType
|
||||||
|
) ?: break
|
||||||
|
|
||||||
if (shouldForceCallableReferenceOrLambdaResolution(completionMode, variableForFixation)) {
|
if (shouldForceCallableReferenceOrLambdaResolution(completionMode, variableForFixation)) {
|
||||||
if (forcePostponedAtomResolution<ResolvedCallableReferenceAtom>(topLevelAtoms, analyze)) continue
|
if (forcePostponedAtomResolution<ResolvedCallableReferenceAtom>(topLevelAtoms, analyze)) continue
|
||||||
if (forcePostponedAtomResolution<LambdaWithTypeVariableAsExpectedTypeAtom>(topLevelAtoms, analyze)) continue
|
if (forcePostponedAtomResolution<LambdaWithTypeVariableAsExpectedTypeAtom>(topLevelAtoms, analyze)) continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variableForFixation != null) {
|
if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) {
|
||||||
if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) {
|
val variableWithConstraints = c.notFixedTypeVariables.getValue(variableForFixation.variable)
|
||||||
val variableWithConstraints = c.notFixedTypeVariables[variableForFixation.variable]!!
|
|
||||||
|
|
||||||
fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives)
|
fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives)
|
||||||
|
|
||||||
if (!variableForFixation.hasProperConstraint) {
|
if (!variableForFixation.hasProperConstraint) {
|
||||||
c.addError(NotEnoughInformationForTypeParameter(variableWithConstraints.typeVariable))
|
c.addError(NotEnoughInformationForTypeParameter(variableWithConstraints.typeVariable))
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,12 +105,10 @@ class KotlinConstraintSystemCompleter(
|
|||||||
|
|
||||||
private fun shouldForceCallableReferenceOrLambdaResolution(
|
private fun shouldForceCallableReferenceOrLambdaResolution(
|
||||||
completionMode: ConstraintSystemCompletionMode,
|
completionMode: ConstraintSystemCompletionMode,
|
||||||
variableForFixation: VariableFixationFinder.VariableForFixation?
|
variableForFixation: VariableFixationFinder.VariableForFixation
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (completionMode == ConstraintSystemCompletionMode.PARTIAL) return false
|
if (completionMode == ConstraintSystemCompletionMode.PARTIAL) return false
|
||||||
if (variableForFixation != null && variableForFixation.hasProperConstraint) return false
|
return !variableForFixation.hasProperConstraint || variableForFixation.hasOnlyTrivialProperConstraint
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// true if we do analyze
|
// true if we do analyze
|
||||||
|
|||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.resolve.calls.inference.components
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintKind
|
||||||
|
import org.jetbrains.kotlin.types.UnwrappedType
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.isNullableNothing
|
||||||
|
|
||||||
|
class TrivialConstraintTypeInferenceOracle {
|
||||||
|
// The idea is to add knowledge that constraint `Nothing(?) <: T` is quite useless and
|
||||||
|
// it's totally fine to go and resolve postponed argument without fixation T to Nothing(?).
|
||||||
|
// In other words, constraint `Nothing(?) <: T` is *not* proper
|
||||||
|
fun isTrivialConstraint(constraint: Constraint): Boolean {
|
||||||
|
// TODO: probably we also can take into account `T <: Any(?)` constraints
|
||||||
|
return constraint.kind == ConstraintKind.LOWER && constraint.type.isNothingOrNullableNothing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UnwrappedType.isNothingOrNullableNothing(): Boolean =
|
||||||
|
isNothing() || isNullableNothing()
|
||||||
+22
-4
@@ -27,13 +27,19 @@ import org.jetbrains.kotlin.types.TypeConstructor
|
|||||||
import org.jetbrains.kotlin.types.UnwrappedType
|
import org.jetbrains.kotlin.types.UnwrappedType
|
||||||
import org.jetbrains.kotlin.types.typeUtil.contains
|
import org.jetbrains.kotlin.types.typeUtil.contains
|
||||||
|
|
||||||
class VariableFixationFinder {
|
class VariableFixationFinder(
|
||||||
|
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle
|
||||||
|
) {
|
||||||
interface Context {
|
interface Context {
|
||||||
val notFixedTypeVariables: Map<TypeConstructor, VariableWithConstraints>
|
val notFixedTypeVariables: Map<TypeConstructor, VariableWithConstraints>
|
||||||
val postponedTypeVariables: List<NewTypeVariable>
|
val postponedTypeVariables: List<NewTypeVariable>
|
||||||
}
|
}
|
||||||
|
|
||||||
data class VariableForFixation(val variable: TypeConstructor, val hasProperConstraint: Boolean)
|
data class VariableForFixation(
|
||||||
|
val variable: TypeConstructor,
|
||||||
|
val hasProperConstraint: Boolean,
|
||||||
|
val hasOnlyTrivialProperConstraint: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
fun findFirstVariableForFixation(
|
fun findFirstVariableForFixation(
|
||||||
c: Context,
|
c: Context,
|
||||||
@@ -46,6 +52,7 @@ class VariableFixationFinder {
|
|||||||
private enum class TypeVariableFixationReadiness {
|
private enum class TypeVariableFixationReadiness {
|
||||||
FORBIDDEN,
|
FORBIDDEN,
|
||||||
WITHOUT_PROPER_ARGUMENT_CONSTRAINT, // proper constraint from arguments -- not from upper bound for type parameters
|
WITHOUT_PROPER_ARGUMENT_CONSTRAINT, // proper constraint from arguments -- not from upper bound for type parameters
|
||||||
|
WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS, // proper trivial constraint from arguments, Nothing <: T
|
||||||
WITH_COMPLEX_DEPENDENCY, // if type variable T has constraint with non fixed type variable inside (non-top-level): T <: Foo<S>
|
WITH_COMPLEX_DEPENDENCY, // if type variable T has constraint with non fixed type variable inside (non-top-level): T <: Foo<S>
|
||||||
RELATED_TO_ANY_OUTPUT_TYPE,
|
RELATED_TO_ANY_OUTPUT_TYPE,
|
||||||
READY_FOR_FIXATION,
|
READY_FOR_FIXATION,
|
||||||
@@ -58,6 +65,7 @@ class VariableFixationFinder {
|
|||||||
!notFixedTypeVariables.contains(variable) ||
|
!notFixedTypeVariables.contains(variable) ||
|
||||||
dependencyProvider.isVariableRelatedToTopLevelType(variable) -> TypeVariableFixationReadiness.FORBIDDEN
|
dependencyProvider.isVariableRelatedToTopLevelType(variable) -> TypeVariableFixationReadiness.FORBIDDEN
|
||||||
!variableHasProperArgumentConstraints(variable) -> TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT
|
!variableHasProperArgumentConstraints(variable) -> TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT
|
||||||
|
variableHasTrivialOrNonProperConstraints(variable) -> TypeVariableFixationReadiness.WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS
|
||||||
hasDependencyToOtherTypeVariables(variable) -> TypeVariableFixationReadiness.WITH_COMPLEX_DEPENDENCY
|
hasDependencyToOtherTypeVariables(variable) -> TypeVariableFixationReadiness.WITH_COMPLEX_DEPENDENCY
|
||||||
dependencyProvider.isVariableRelatedToAnyOutputType(variable) -> TypeVariableFixationReadiness.RELATED_TO_ANY_OUTPUT_TYPE
|
dependencyProvider.isVariableRelatedToAnyOutputType(variable) -> TypeVariableFixationReadiness.RELATED_TO_ANY_OUTPUT_TYPE
|
||||||
else -> TypeVariableFixationReadiness.READY_FOR_FIXATION
|
else -> TypeVariableFixationReadiness.READY_FOR_FIXATION
|
||||||
@@ -65,12 +73,12 @@ class VariableFixationFinder {
|
|||||||
|
|
||||||
private fun Context.findTypeVariableForFixation(
|
private fun Context.findTypeVariableForFixation(
|
||||||
allTypeVariables: List<TypeConstructor>,
|
allTypeVariables: List<TypeConstructor>,
|
||||||
postponedKtPrimitives: List<PostponedResolvedAtom>,
|
postponedArguments: List<PostponedResolvedAtom>,
|
||||||
completionMode: ConstraintSystemCompletionMode,
|
completionMode: ConstraintSystemCompletionMode,
|
||||||
topLevelType: UnwrappedType
|
topLevelType: UnwrappedType
|
||||||
): VariableForFixation? {
|
): VariableForFixation? {
|
||||||
val dependencyProvider = TypeVariableDependencyInformationProvider(
|
val dependencyProvider = TypeVariableDependencyInformationProvider(
|
||||||
notFixedTypeVariables, postponedKtPrimitives, topLevelType.takeIf { completionMode == PARTIAL }
|
notFixedTypeVariables, postponedArguments, topLevelType.takeIf { completionMode == PARTIAL }
|
||||||
)
|
)
|
||||||
|
|
||||||
val candidate = allTypeVariables.maxBy { getTypeVariableReadiness(it, dependencyProvider) } ?: return null
|
val candidate = allTypeVariables.maxBy { getTypeVariableReadiness(it, dependencyProvider) } ?: return null
|
||||||
@@ -79,6 +87,9 @@ class VariableFixationFinder {
|
|||||||
return when (candidateReadiness) {
|
return when (candidateReadiness) {
|
||||||
TypeVariableFixationReadiness.FORBIDDEN -> null
|
TypeVariableFixationReadiness.FORBIDDEN -> null
|
||||||
TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT -> VariableForFixation(candidate, false)
|
TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT -> VariableForFixation(candidate, false)
|
||||||
|
TypeVariableFixationReadiness.WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS ->
|
||||||
|
VariableForFixation(candidate, hasProperConstraint = true, hasOnlyTrivialProperConstraint = true)
|
||||||
|
|
||||||
else -> VariableForFixation(candidate, true)
|
else -> VariableForFixation(candidate, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +103,13 @@ class VariableFixationFinder {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Context.variableHasTrivialOrNonProperConstraints(variable: TypeConstructor): Boolean {
|
||||||
|
return notFixedTypeVariables[variable]?.constraints?.all { constraint ->
|
||||||
|
val isProperConstraint = isProperArgumentConstraint(constraint)
|
||||||
|
isProperConstraint && trivialConstraintTypeInferenceOracle.isTrivialConstraint(constraint) || !isProperConstraint
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
private fun Context.variableHasProperArgumentConstraints(variable: TypeConstructor): Boolean =
|
private fun Context.variableHasProperArgumentConstraints(variable: TypeConstructor): Boolean =
|
||||||
notFixedTypeVariables[variable]?.constraints?.any { isProperArgumentConstraint(it) } ?: false
|
notFixedTypeVariables[variable]?.constraints?.any { isProperArgumentConstraint(it) } ?: false
|
||||||
|
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// !LANGUAGE: +NewInference
|
||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
// Validation test.
|
||||||
|
// Check that type variable is fixed to Data<Nothing> as it's used in input types for lambda
|
||||||
|
|
||||||
|
class Data<T>(val x: T) {
|
||||||
|
fun dataMethod() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <K> bar(x: K, y: (K) -> Unit) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
bar(Data(null)) { it.dataMethod() }
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun </*0*/ K> bar(/*0*/ x: K, /*1*/ y: (K) -> kotlin.Unit): kotlin.Unit
|
||||||
|
public fun test(): kotlin.Unit
|
||||||
|
|
||||||
|
public final class Data</*0*/ T> {
|
||||||
|
public constructor Data</*0*/ T>(/*0*/ x: T)
|
||||||
|
public final val x: T
|
||||||
|
public final fun dataMethod(): kotlin.Unit
|
||||||
|
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
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
// !LANGUAGE: +NewInference
|
||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER
|
||||||
|
|
||||||
|
fun <K> select2(x: K, y: K): K = TODO()
|
||||||
|
fun <K> select3(x: K, y: K, z: K): K = TODO()
|
||||||
|
|
||||||
|
fun <K : S, S> dependantSelect2(x: K, y: S) {}
|
||||||
|
fun <K : S, S> dependantSelect3(x: K, y: K, z: S) {}
|
||||||
|
|
||||||
|
fun foo() {}
|
||||||
|
fun cloneFoo() {}
|
||||||
|
fun bar(x: Int) {}
|
||||||
|
|
||||||
|
fun test(f1: (Int) -> Unit, f2: kotlin.Function1<Int, Unit>) {
|
||||||
|
select2(null, ::foo)
|
||||||
|
select3(null, f1, ::bar)
|
||||||
|
select3(null, f2, ::bar)
|
||||||
|
|
||||||
|
select3(null, f1, <!TYPE_MISMATCH!>::foo<!>)
|
||||||
|
select3(null, f2, <!TYPE_MISMATCH!>::foo<!>)
|
||||||
|
|
||||||
|
dependantSelect2(null, ::foo)
|
||||||
|
dependantSelect3(null, ::foo, ::cloneFoo)
|
||||||
|
dependantSelect3(null, f1, ::bar)
|
||||||
|
// Here we have LOWER(Nothing?), UPPER(Function1) and therefore type variable is fixed to the former
|
||||||
|
dependantSelect3(null, <!TYPE_MISMATCH!>::bar<!>, f1)
|
||||||
|
|
||||||
|
// These errors are actually can be fixed (and, probably, should) if we force resolution of callable reference
|
||||||
|
dependantSelect3(null, ::foo, <!TYPE_MISMATCH!>::bar<!>)
|
||||||
|
dependantSelect3(null, ::bar, <!TYPE_MISMATCH!>::foo<!>)
|
||||||
|
dependantSelect3(null, f1, <!TYPE_MISMATCH!>::foo<!>)
|
||||||
|
dependantSelect3(null, <!TYPE_MISMATCH!>::foo<!>, f1)
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
public fun bar(/*0*/ x: kotlin.Int): kotlin.Unit
|
||||||
|
public fun cloneFoo(): kotlin.Unit
|
||||||
|
public fun </*0*/ K : S, /*1*/ S> dependantSelect2(/*0*/ x: K, /*1*/ y: S): kotlin.Unit
|
||||||
|
public fun </*0*/ K : S, /*1*/ S> dependantSelect3(/*0*/ x: K, /*1*/ y: K, /*2*/ z: S): kotlin.Unit
|
||||||
|
public fun foo(): kotlin.Unit
|
||||||
|
public fun </*0*/ K> select2(/*0*/ x: K, /*1*/ y: K): K
|
||||||
|
public fun </*0*/ K> select3(/*0*/ x: K, /*1*/ y: K, /*2*/ z: K): K
|
||||||
|
public fun test(/*0*/ f1: (kotlin.Int) -> kotlin.Unit, /*1*/ f2: (kotlin.Int) -> kotlin.Unit): kotlin.Unit
|
||||||
@@ -9945,6 +9945,29 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/tests/inference/nothingType")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class NothingType extends AbstractDiagnosticsTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInNothingType() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/nothingType"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("complexDependancyOnVariableWithTrivialConstraint.kt")
|
||||||
|
public void testComplexDependancyOnVariableWithTrivialConstraint() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nothingWithCallableReference.kt")
|
||||||
|
public void testNothingWithCallableReference() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/recursiveCalls")
|
@TestMetadata("compiler/testData/diagnostics/tests/inference/recursiveCalls")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Generated
+23
@@ -9945,6 +9945,29 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/diagnostics/tests/inference/nothingType")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class NothingType extends AbstractDiagnosticsUsingJavacTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInNothingType() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/tests/inference/nothingType"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("complexDependancyOnVariableWithTrivialConstraint.kt")
|
||||||
|
public void testComplexDependancyOnVariableWithTrivialConstraint() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("nothingWithCallableReference.kt")
|
||||||
|
public void testNothingWithCallableReference() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/diagnostics/tests/inference/recursiveCalls")
|
@TestMetadata("compiler/testData/diagnostics/tests/inference/recursiveCalls")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user