[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:
Generated
+5
@@ -10662,6 +10662,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
|
||||
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")
|
||||
public void testTransitiveConstraint() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt");
|
||||
|
||||
+28
-11
@@ -52,7 +52,7 @@ class CompletionModeCalculator {
|
||||
private val candidate: KotlinResolutionCandidate,
|
||||
private val returnType: UnwrappedType?,
|
||||
private val csCompleterContext: CsCompleterContext,
|
||||
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle
|
||||
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle,
|
||||
) {
|
||||
private enum class FixationDirection {
|
||||
TO_SUBTYPE, EQUALITY
|
||||
@@ -192,13 +192,32 @@ class CompletionModeCalculator {
|
||||
val constraints = variableWithConstraints.constraints
|
||||
val variable = variableWithConstraints.typeVariable
|
||||
|
||||
// todo check correctness for @Exact
|
||||
return constraints.isNotEmpty() && constraints.any { constraint ->
|
||||
constraint.hasRequiredKind(direction)
|
||||
&& isProperType(constraint.type)
|
||||
&& !constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()
|
||||
&& !isNothingConstraintForPartiallyAnalyzedVariable(constraint, variable)
|
||||
// ILT constraint tracking is necessary to prevent incorrect full completion from Nothing constraint
|
||||
// Consider ILT <: T; Nothing <: T for T requiring lower constraint
|
||||
// Nothing would trigger full completion, but resulting type would be Int
|
||||
// Possible restrictions on integer constant from outer calls would be ignored
|
||||
|
||||
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) {
|
||||
@@ -206,13 +225,11 @@ class CompletionModeCalculator {
|
||||
FixationDirection.EQUALITY -> kind.isEqual()
|
||||
}
|
||||
|
||||
private fun CsCompleterContext.isNothingConstraintForPartiallyAnalyzedVariable(
|
||||
private fun CsCompleterContext.isLowerConstraintForPartiallyAnalyzedVariable(
|
||||
constraint: Constraint,
|
||||
variable: TypeVariableMarker
|
||||
): Boolean {
|
||||
if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(constraint.type) || !constraint.kind.isLower())
|
||||
return false
|
||||
return postponedAtoms.any { atom ->
|
||||
return constraint.kind.isLower() && postponedAtoms.any { atom ->
|
||||
atom.expectedType?.contains { type -> variable.defaultType() == type } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -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
|
||||
}
|
||||
}
|
||||
+44
@@ -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
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package
|
||||
|
||||
public fun test(/*0*/ boolean: kotlin.Boolean): kotlin.Unit
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
// !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) {
|
||||
1 -> 1
|
||||
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");
|
||||
}
|
||||
|
||||
@TestMetadata("partialForIltWithNothing.kt")
|
||||
public void testPartialForIltWithNothing() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIltWithNothing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("transitiveConstraint.kt")
|
||||
public void testTransitiveConstraint() throws Exception {
|
||||
runTest("compiler/testData/diagnostics/tests/inference/completion/transitiveConstraint.kt");
|
||||
|
||||
Generated
+5
@@ -10664,6 +10664,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
|
||||
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")
|
||||
public void testTransitiveConstraint() throws Exception {
|
||||
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)
|
||||
} else {
|
||||
// Here we just check that we haven't crashed due to exception
|
||||
0L
|
||||
0
|
||||
}
|
||||
} finally {
|
||||
if (configured) {
|
||||
|
||||
Reference in New Issue
Block a user