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 a12cafec84f..8671de4befd 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 @@ -69,27 +69,27 @@ class KotlinConstraintSystemCompleter( val allTypeVariables = getOrderedAllTypeVariables(c, collectVariablesFromContext, topLevelAtoms) val postponedKtPrimitives = getOrderedNotAnalyzedPostponedArguments(topLevelAtoms) - val variableForFixation = variableFixationFinder.findFirstVariableForFixation( - c, allTypeVariables, postponedKtPrimitives, completionMode, topLevelType - ) + val variableForFixation = + variableFixationFinder.findFirstVariableForFixation( + c, allTypeVariables, postponedKtPrimitives, completionMode, topLevelType + ) ?: break if (shouldForceCallableReferenceOrLambdaResolution(completionMode, variableForFixation)) { if (forcePostponedAtomResolution(topLevelAtoms, analyze)) continue if (forcePostponedAtomResolution(topLevelAtoms, analyze)) continue } - if (variableForFixation != null) { - if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) { - val variableWithConstraints = c.notFixedTypeVariables[variableForFixation.variable]!! + if (variableForFixation.hasProperConstraint || completionMode == ConstraintSystemCompletionMode.FULL) { + val variableWithConstraints = c.notFixedTypeVariables.getValue(variableForFixation.variable) - fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives) + fixVariable(c, topLevelType, variableWithConstraints, postponedKtPrimitives) - if (!variableForFixation.hasProperConstraint) { - c.addError(NotEnoughInformationForTypeParameter(variableWithConstraints.typeVariable)) - } - continue + if (!variableForFixation.hasProperConstraint) { + c.addError(NotEnoughInformationForTypeParameter(variableWithConstraints.typeVariable)) } + continue } + break } @@ -105,12 +105,10 @@ class KotlinConstraintSystemCompleter( private fun shouldForceCallableReferenceOrLambdaResolution( completionMode: ConstraintSystemCompletionMode, - variableForFixation: VariableFixationFinder.VariableForFixation? + variableForFixation: VariableFixationFinder.VariableForFixation ): Boolean { if (completionMode == ConstraintSystemCompletionMode.PARTIAL) return false - if (variableForFixation != null && variableForFixation.hasProperConstraint) return false - - return true + return !variableForFixation.hasProperConstraint || variableForFixation.hasOnlyTrivialProperConstraint } // true if we do analyze diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/TrivialConstraintTypeInferenceOracle.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/TrivialConstraintTypeInferenceOracle.kt new file mode 100644 index 00000000000..417447504fb --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/TrivialConstraintTypeInferenceOracle.kt @@ -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() \ No newline at end of file diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/VariableFixationFinder.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/VariableFixationFinder.kt index 65aad65cb04..5a04e974d0f 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/VariableFixationFinder.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/calls/inference/components/VariableFixationFinder.kt @@ -27,13 +27,19 @@ import org.jetbrains.kotlin.types.TypeConstructor import org.jetbrains.kotlin.types.UnwrappedType import org.jetbrains.kotlin.types.typeUtil.contains -class VariableFixationFinder { +class VariableFixationFinder( + private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle +) { interface Context { val notFixedTypeVariables: Map val postponedTypeVariables: List } - data class VariableForFixation(val variable: TypeConstructor, val hasProperConstraint: Boolean) + data class VariableForFixation( + val variable: TypeConstructor, + val hasProperConstraint: Boolean, + val hasOnlyTrivialProperConstraint: Boolean = false + ) fun findFirstVariableForFixation( c: Context, @@ -46,6 +52,7 @@ class VariableFixationFinder { private enum class TypeVariableFixationReadiness { FORBIDDEN, 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 RELATED_TO_ANY_OUTPUT_TYPE, READY_FOR_FIXATION, @@ -58,6 +65,7 @@ class VariableFixationFinder { !notFixedTypeVariables.contains(variable) || dependencyProvider.isVariableRelatedToTopLevelType(variable) -> TypeVariableFixationReadiness.FORBIDDEN !variableHasProperArgumentConstraints(variable) -> TypeVariableFixationReadiness.WITHOUT_PROPER_ARGUMENT_CONSTRAINT + variableHasTrivialOrNonProperConstraints(variable) -> TypeVariableFixationReadiness.WITH_TRIVIAL_OR_NON_PROPER_CONSTRAINTS hasDependencyToOtherTypeVariables(variable) -> TypeVariableFixationReadiness.WITH_COMPLEX_DEPENDENCY dependencyProvider.isVariableRelatedToAnyOutputType(variable) -> TypeVariableFixationReadiness.RELATED_TO_ANY_OUTPUT_TYPE else -> TypeVariableFixationReadiness.READY_FOR_FIXATION @@ -65,12 +73,12 @@ class VariableFixationFinder { private fun Context.findTypeVariableForFixation( allTypeVariables: List, - postponedKtPrimitives: List, + postponedArguments: List, completionMode: ConstraintSystemCompletionMode, topLevelType: UnwrappedType ): VariableForFixation? { val dependencyProvider = TypeVariableDependencyInformationProvider( - notFixedTypeVariables, postponedKtPrimitives, topLevelType.takeIf { completionMode == PARTIAL } + notFixedTypeVariables, postponedArguments, topLevelType.takeIf { completionMode == PARTIAL } ) val candidate = allTypeVariables.maxBy { getTypeVariableReadiness(it, dependencyProvider) } ?: return null @@ -79,6 +87,9 @@ class VariableFixationFinder { return when (candidateReadiness) { TypeVariableFixationReadiness.FORBIDDEN -> null 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) } } @@ -92,6 +103,13 @@ class VariableFixationFinder { 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 = notFixedTypeVariables[variable]?.constraints?.any { isProperArgumentConstraint(it) } ?: false diff --git a/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.kt b/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.kt new file mode 100644 index 00000000000..6e5b1719e8f --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.kt @@ -0,0 +1,15 @@ +// !LANGUAGE: +NewInference +// !DIAGNOSTICS: -UNUSED_PARAMETER + +// Validation test. +// Check that type variable is fixed to Data as it's used in input types for lambda + +class Data(val x: T) { + fun dataMethod() {} +} + +fun bar(x: K, y: (K) -> Unit) {} + +fun test() { + bar(Data(null)) { it.dataMethod() } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.txt b/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.txt new file mode 100644 index 00000000000..927d73437b9 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/nothingType/complexDependancyOnVariableWithTrivialConstraint.txt @@ -0,0 +1,13 @@ +package + +public fun bar(/*0*/ x: K, /*1*/ y: (K) -> kotlin.Unit): kotlin.Unit +public fun test(): kotlin.Unit + +public final class Data { + public constructor Data(/*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 +} diff --git a/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.kt b/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.kt new file mode 100644 index 00000000000..ec96bfb1418 --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.kt @@ -0,0 +1,33 @@ +// !LANGUAGE: +NewInference +// !DIAGNOSTICS: -UNUSED_PARAMETER + +fun select2(x: K, y: K): K = TODO() +fun select3(x: K, y: K, z: K): K = TODO() + +fun dependantSelect2(x: K, y: S) {} +fun dependantSelect3(x: K, y: K, z: S) {} + +fun foo() {} +fun cloneFoo() {} +fun bar(x: Int) {} + +fun test(f1: (Int) -> Unit, f2: kotlin.Function1) { + select2(null, ::foo) + select3(null, f1, ::bar) + select3(null, f2, ::bar) + + select3(null, f1, ::foo) + select3(null, f2, ::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, ::bar, f1) + + // These errors are actually can be fixed (and, probably, should) if we force resolution of callable reference + dependantSelect3(null, ::foo, ::bar) + dependantSelect3(null, ::bar, ::foo) + dependantSelect3(null, f1, ::foo) + dependantSelect3(null, ::foo, f1) +} diff --git a/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.txt b/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.txt new file mode 100644 index 00000000000..074fbcca2af --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/nothingType/nothingWithCallableReference.txt @@ -0,0 +1,10 @@ +package + +public fun bar(/*0*/ x: kotlin.Int): kotlin.Unit +public fun cloneFoo(): kotlin.Unit +public fun dependantSelect2(/*0*/ x: K, /*1*/ y: S): kotlin.Unit +public fun dependantSelect3(/*0*/ x: K, /*1*/ y: K, /*2*/ z: S): kotlin.Unit +public fun foo(): kotlin.Unit +public fun select2(/*0*/ x: K, /*1*/ y: K): K +public fun 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 diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 60d319da981..8783b5e8bef 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index d8621ff8a48..dd204d970fd 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -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") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)