[NI] Fix completion for ILT when Nothing constraint is present

Full completion should not be done if lower `Nothing`
is the only proper constraint when constraint with ILT type is present.
ILT will be selected as a resulting type and transformed into `Int`
without attention to possible restrictions from outer calls.
This commit is contained in:
Pavel Kirpichenkov
2020-02-14 13:37:12 +03:00
parent 208c06516b
commit 4a7b4d655c
9 changed files with 136 additions and 13 deletions
@@ -10662,6 +10662,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
} }
@TestMetadata("partialForIltWithNothing.kt")
public void testPartialForIltWithNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIltWithNothing.kt");
}
@TestMetadata("transitiveConstraint.kt") @TestMetadata("transitiveConstraint.kt")
public void testTransitiveConstraint() throws Exception { public void testTransitiveConstraint() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt");
@@ -52,7 +52,7 @@ class CompletionModeCalculator {
private val candidate: KotlinResolutionCandidate, private val candidate: KotlinResolutionCandidate,
private val returnType: UnwrappedType?, private val returnType: UnwrappedType?,
private val csCompleterContext: CsCompleterContext, private val csCompleterContext: CsCompleterContext,
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle,
) { ) {
private enum class FixationDirection { private enum class FixationDirection {
TO_SUBTYPE, EQUALITY TO_SUBTYPE, EQUALITY
@@ -192,13 +192,32 @@ class CompletionModeCalculator {
val constraints = variableWithConstraints.constraints val constraints = variableWithConstraints.constraints
val variable = variableWithConstraints.typeVariable val variable = variableWithConstraints.typeVariable
// todo check correctness for @Exact // ILT constraint tracking is necessary to prevent incorrect full completion from Nothing constraint
return constraints.isNotEmpty() && constraints.any { constraint -> // Consider ILT <: T; Nothing <: T for T requiring lower constraint
constraint.hasRequiredKind(direction) // Nothing would trigger full completion, but resulting type would be Int
&& isProperType(constraint.type) // Possible restrictions on integer constant from outer calls would be ignored
&& !constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()
&& !isNothingConstraintForPartiallyAnalyzedVariable(constraint, variable) var iltConstraintPresent = false
var properConstraintPresent = false
var nonNothingProperConstraintPresent = false
for (constraint in constraints) {
if (!constraint.hasRequiredKind(direction) || !isProperType(constraint.type))
continue
if (constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()) {
iltConstraintPresent = true
} else if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(constraint.type)) {
properConstraintPresent = true
nonNothingProperConstraintPresent = true
} else if (!isLowerConstraintForPartiallyAnalyzedVariable(constraint, variable)) {
properConstraintPresent = true
}
} }
if (!properConstraintPresent) return false
return !iltConstraintPresent || nonNothingProperConstraintPresent
} }
private fun Constraint.hasRequiredKind(direction: FixationDirection) = when (direction) { private fun Constraint.hasRequiredKind(direction: FixationDirection) = when (direction) {
@@ -206,13 +225,11 @@ class CompletionModeCalculator {
FixationDirection.EQUALITY -> kind.isEqual() FixationDirection.EQUALITY -> kind.isEqual()
} }
private fun CsCompleterContext.isNothingConstraintForPartiallyAnalyzedVariable( private fun CsCompleterContext.isLowerConstraintForPartiallyAnalyzedVariable(
constraint: Constraint, constraint: Constraint,
variable: TypeVariableMarker variable: TypeVariableMarker
): Boolean { ): Boolean {
if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(constraint.type) || !constraint.kind.isLower()) return constraint.kind.isLower() && postponedAtoms.any { atom ->
return false
return postponedAtoms.any { atom ->
atom.expectedType?.contains { type -> variable.defaultType() == type } ?: false atom.expectedType?.contains { type -> variable.defaultType() == type } ?: false
} }
} }
@@ -0,0 +1,44 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun test(boolean: Boolean) {
val expectedLong: Long = if (boolean) {
if (boolean) {
42
} else {
return
}
} else {
return
}
val expectedInt: Int = if (boolean) {
if (boolean) {
42
} else {
return
}
} else {
return
}
val expectedShort: Short = if (boolean) {
if (boolean) {
42
} else {
return
}
} else {
return
}
val expectedByte: Byte = if (boolean) {
if (boolean) {
42
} else {
return
}
} else {
return
}
}
@@ -0,0 +1,44 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun test(boolean: Boolean) {
val expectedLong: Long = if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Long")!>if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Long")!>42<!>
} else {
return
}<!>
} else {
return
}
val expectedInt: Int = if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int")!>42<!>
} else {
return
}<!>
} else {
return
}
val expectedShort: Short = if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Short")!>if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Short")!>42<!>
} else {
return
}<!>
} else {
return
}
val expectedByte: Byte = if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Byte")!>if (boolean) {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Byte")!>42<!>
} else {
return
}<!>
} else {
return
}
}
@@ -0,0 +1,3 @@
package
public fun test(/*0*/ boolean: kotlin.Boolean): kotlin.Unit
+1 -1
View File
@@ -1,5 +1,5 @@
// !WITH_NEW_INFERENCE // !WITH_NEW_INFERENCE
val test: Int = <!NI;TYPE_MISMATCH, NI;TYPE_MISMATCH!>if (true) { val test: Int = <!NI;TYPE_MISMATCH, NI;TYPE_MISMATCH, NI;TYPE_MISMATCH!>if (true) {
when (2) { when (2) {
1 -> 1 1 -> 1
else -> <!OI;NULL_FOR_NONNULL_TYPE!>null<!> else -> <!OI;NULL_FOR_NONNULL_TYPE!>null<!>
@@ -10669,6 +10669,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
} }
@TestMetadata("partialForIltWithNothing.kt")
public void testPartialForIltWithNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIltWithNothing.kt");
}
@TestMetadata("transitiveConstraint.kt") @TestMetadata("transitiveConstraint.kt")
public void testTransitiveConstraint() throws Exception { public void testTransitiveConstraint() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt");
@@ -10664,6 +10664,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
} }
@TestMetadata("partialForIltWithNothing.kt")
public void testPartialForIltWithNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIltWithNothing.kt");
}
@TestMetadata("transitiveConstraint.kt") @TestMetadata("transitiveConstraint.kt")
public void testTransitiveConstraint() throws Exception { public void testTransitiveConstraint() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt"); runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt");
@@ -39,7 +39,7 @@ abstract class AbstractFirPsiCheckerTest : AbstractPsiCheckerTest() {
throw FileComparisonFailure(e.message, e.expected, e.actual, File(e.filePath).absolutePath) throw FileComparisonFailure(e.message, e.expected, e.actual, File(e.filePath).absolutePath)
} else { } else {
// Here we just check that we haven't crashed due to exception // Here we just check that we haven't crashed due to exception
0L 0
} }
} finally { } finally {
if (configured) { if (configured) {