[NI] Soften restictions on using Nothing as proper constraint for full call completion

Consider lower `Nothing` constraint non-proper only if there is a dependant not analyzed postponed atom.
Early completion to `Nothing` provides data flow info for smart casts.

KT-35668 Fixed
This commit is contained in:
Pavel Kirpichenkov
2020-01-22 15:46:33 +03:00
parent f1d9177112
commit 78c9bbcc0d
13 changed files with 234 additions and 24 deletions
@@ -10582,6 +10582,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
runTest("compiler/testData/diagnostics/tests/inference/completion/kt33166.kt");
}
@TestMetadata("lambdaWithVariableAndNothing.kt")
public void testLambdaWithVariableAndNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/lambdaWithVariableAndNothing.kt");
}
@TestMetadata("partialForIlt.kt")
public void testPartialForIlt() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
@@ -17846,6 +17851,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
runTest("compiler/testData/diagnostics/tests/regressions/kt3535.kt");
}
@TestMetadata("kt35668.kt")
public void testKt35668() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt35668.kt");
}
@TestMetadata("kt3647.kt")
public void testKt3647() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt3647.kt");
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.components.TrivialConstraint
import org.jetbrains.kotlin.resolve.calls.inference.model.Constraint
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.KotlinResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.model.PostponedResolvedAtom
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.UnwrappedType
import org.jetbrains.kotlin.types.model.*
@@ -39,11 +40,14 @@ class CompletionModeCalculator {
if (csBuilder.isProperType(returnType)) return ConstraintSystemCompletionMode.FULL
// For nested call with variables in return type check possibility of full completion
return CalculatorForNestedCall(returnType, csCompleterContext, trivialConstraintTypeInferenceOracle).computeCompletionMode()
return CalculatorForNestedCall(
candidate, returnType, csCompleterContext, trivialConstraintTypeInferenceOracle
).computeCompletionMode()
}
}
private class CalculatorForNestedCall(
private val candidate: KotlinResolutionCandidate,
private val returnType: UnwrappedType?,
private val csCompleterContext: CsCompleterContext,
private val trivialConstraintTypeInferenceOracle: TrivialConstraintTypeInferenceOracle
@@ -57,6 +61,10 @@ class CompletionModeCalculator {
private val variablesWithQueuedConstraints = mutableSetOf<TypeVariableMarker>()
private val typesToProcess: Queue<KotlinTypeMarker> = ArrayDeque()
private val postponedAtoms: List<PostponedResolvedAtom> by lazy {
KotlinConstraintSystemCompleter.getOrderedNotAnalyzedPostponedArguments(listOf(candidate.resolvedCall))
}
fun computeCompletionMode(): ConstraintSystemCompletionMode = with(csCompleterContext) {
// Add fixation directions for variables based on effective variance in type
typesToProcess.add(returnType)
@@ -163,13 +171,14 @@ class CompletionModeCalculator {
direction: FixationDirection
): Boolean {
val constraints = variableWithConstraints.constraints
val variable = variableWithConstraints.typeVariable
// todo check correctness for @Exact
return constraints.isNotEmpty() && constraints.any { constraint ->
constraint.hasRequiredKind(direction)
&& !constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()
&& isProperType(constraint.type)
&& trivialConstraintTypeInferenceOracle.isSuitableResultedType(constraint.type)
&& !constraint.type.typeConstructor().isIntegerLiteralTypeConstructor()
&& !isNothingConstraintForPartiallyAnalyzedVariable(constraint, variable)
}
}
@@ -177,5 +186,16 @@ class CompletionModeCalculator {
FixationDirection.TO_SUBTYPE -> kind.isLower() || kind.isEqual()
FixationDirection.EQUALITY -> kind.isEqual()
}
private fun CsCompleterContext.isNothingConstraintForPartiallyAnalyzedVariable(
constraint: Constraint,
variable: TypeVariableMarker
): Boolean {
if (trivialConstraintTypeInferenceOracle.isSuitableResultedType(constraint.type) || !constraint.kind.isLower())
return false
return postponedAtoms.any { atom ->
atom.expectedType?.contains { type -> variable.defaultType() == type } ?: false
}
}
}
}
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.resolve.calls.inference.components
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalTypeOrSubtype
import org.jetbrains.kotlin.resolve.calls.components.KotlinResolutionStatelessCallbacks
import org.jetbrains.kotlin.resolve.calls.components.transformToResolvedLambda
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
import org.jetbrains.kotlin.resolve.calls.inference.model.*
@@ -23,7 +22,6 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class KotlinConstraintSystemCompleter(
private val resultTypeResolver: ResultTypeResolver,
val variableFixationFinder: VariableFixationFinder,
private val statelessCallbacks: KotlinResolutionStatelessCallbacks
) {
enum class ConstraintSystemCompletionMode {
FULL,
@@ -214,23 +212,6 @@ class KotlinConstraintSystemCompleter(
return false
}
private fun getOrderedNotAnalyzedPostponedArguments(topLevelAtoms: List<ResolvedAtom>): List<PostponedResolvedAtom> {
fun ResolvedAtom.process(to: MutableList<PostponedResolvedAtom>) {
to.addIfNotNull(this.safeAs<PostponedResolvedAtom>()?.takeUnless { it.analyzed })
if (analyzed) {
subResolvedAtoms?.forEach { it.process(to) }
}
}
val notAnalyzedArguments = arrayListOf<PostponedResolvedAtom>()
for (primitive in topLevelAtoms) {
primitive.process(notAnalyzedArguments)
}
return notAnalyzedArguments
}
private fun getOrderedAllTypeVariables(
c: Context,
collectVariablesFromContext: Boolean,
@@ -352,4 +333,23 @@ class KotlinConstraintSystemCompleter(
return null
}
companion object {
fun getOrderedNotAnalyzedPostponedArguments(topLevelAtoms: List<ResolvedAtom>): List<PostponedResolvedAtom> {
fun ResolvedAtom.process(to: MutableList<PostponedResolvedAtom>) {
to.addIfNotNull(this.safeAs<PostponedResolvedAtom>()?.takeUnless { it.analyzed })
if (analyzed) {
subResolvedAtoms?.forEach { it.process(to) }
}
}
val notAnalyzedArguments = arrayListOf<PostponedResolvedAtom>()
for (primitive in topLevelAtoms) {
primitive.process(notAnalyzedArguments)
}
return notAnalyzedArguments
}
}
}
@@ -15,7 +15,7 @@ val ww = if (true) {
<!OI;TYPE_MISMATCH!>{ true }<!> <!USELESS_ELVIS!>?: null!!<!>
}
else if (true) {
<!NI;TYPE_MISMATCH, NI;TYPE_MISMATCH, TYPE_MISMATCH!>{ <!NI;CONSTANT_EXPECTED_TYPE_MISMATCH!>true<!> }<!> <!USELESS_ELVIS!>?: null!!<!>
<!OI;TYPE_MISMATCH!>{ true }<!> <!USELESS_ELVIS!>?: null!!<!>
}
else {
null!!
@@ -0,0 +1,52 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun <K> id(it: K) = it
fun <E> smartCast(arg: E?, fn: () -> Any?): E = TODO()
fun <E1> noSmartCast1(arg: E1?, fn: () -> E1): E1 = TODO()
fun <E2> noSmartCast2(arg: E2?, fn: E2): E2 = TODO()
fun <E3, F : E3> noSmartCast3(arg: E3?, fn: () -> F): E3 = TODO()
fun <E4, F : E4> noSmartCast4(arg: E4?, fn: F): E4 = TODO()
fun testSmartCast(s: String?) {
id(
if (s != null) ""
else smartCast(null) { "" }
)
s.length
}
fun testNoSmartCast1(s: String?) {
id(
if (s != null) ""
else noSmartCast1(null) { "" }
)
s.<!INAPPLICABLE_CANDIDATE!>length<!>
}
fun testNoSmartCast2(s: String?) {
id(
if (s != null) ( {""} )
else noSmartCast2(null) { "" }
)
s.<!INAPPLICABLE_CANDIDATE!>length<!>
}
fun testNoSmartCast3(s: String?) {
id(
if (s != null) ""
else noSmartCast3(null) { "" }
)
s.<!INAPPLICABLE_CANDIDATE!>length<!>
}
// KT-36069
fun testNoSmartCast4(s: String?) {
id(
if (s != null) ( {""} )
else noSmartCast4(null) { "" }
)
s.<!INAPPLICABLE_CANDIDATE!>length<!>
}
@@ -0,0 +1,52 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun <K> id(it: K) = it
fun <E> smartCast(arg: E?, fn: () -> Any?): E = TODO()
fun <E1> noSmartCast1(arg: E1?, fn: () -> E1): E1 = TODO()
fun <E2> noSmartCast2(arg: E2?, fn: E2): E2 = TODO()
fun <E3, F : E3> noSmartCast3(arg: E3?, fn: () -> F): E3 = TODO()
fun <E4, F : E4> noSmartCast4(arg: E4?, fn: F): E4 = TODO()
fun testSmartCast(s: String?) {
id(
if (s != null) ""
else <!IMPLICIT_NOTHING_AS_TYPE_PARAMETER!>smartCast<!>(null) { "" }
)
<!DEBUG_INFO_SMARTCAST!>s<!>.length
}
fun testNoSmartCast1(s: String?) {
id(
if (s != null) ""
else noSmartCast1(null) { "" }
)
s<!UNSAFE_CALL!>.<!>length
}
fun testNoSmartCast2(s: String?) {
id(
if (s != null) ( {""} )
else noSmartCast2(null) { "" }
)
s<!UNSAFE_CALL!>.<!>length
}
fun testNoSmartCast3(s: String?) {
id(
if (s != null) ""
else noSmartCast3(null) { "" }
)
s<!UNSAFE_CALL!>.<!>length
}
// KT-36069
fun testNoSmartCast4(s: String?) {
id(
if (s != null) ( {""} )
else <!IMPLICIT_NOTHING_AS_TYPE_PARAMETER, IMPLICIT_NOTHING_AS_TYPE_PARAMETER!>noSmartCast4<!>(null) <!TYPE_MISMATCH!>{ <!TYPE_MISMATCH!>""<!> }<!>
)
s<!UNSAFE_CALL!>.<!>length
}
@@ -0,0 +1,13 @@
package
public fun </*0*/ K> id(/*0*/ it: K): K
public fun </*0*/ E1> noSmartCast1(/*0*/ arg: E1?, /*1*/ fn: () -> E1): E1
public fun </*0*/ E2> noSmartCast2(/*0*/ arg: E2?, /*1*/ fn: E2): E2
public fun </*0*/ E3, /*1*/ F : E3> noSmartCast3(/*0*/ arg: E3?, /*1*/ fn: () -> F): E3
public fun </*0*/ E4, /*1*/ F : E4> noSmartCast4(/*0*/ arg: E4?, /*1*/ fn: F): E4
public fun </*0*/ E> smartCast(/*0*/ arg: E?, /*1*/ fn: () -> kotlin.Any?): E
public fun testNoSmartCast1(/*0*/ s: kotlin.String?): kotlin.Unit
public fun testNoSmartCast2(/*0*/ s: kotlin.String?): kotlin.Unit
public fun testNoSmartCast3(/*0*/ s: kotlin.String?): kotlin.Unit
public fun testNoSmartCast4(/*0*/ s: kotlin.String?): kotlin.Unit
public fun testSmartCast(/*0*/ s: kotlin.String?): kotlin.Unit
@@ -0,0 +1,20 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun main() {
val baseDir: String? = ""
val networkParameters: String? = ""
if (baseDir != null) {
if (networkParameters != null) {
Unit
} else if (true){
return
} else {
return
}
} else {
return
}
networkParameters.length // unsafe call
}
@@ -0,0 +1,20 @@
// !LANGUAGE: +NewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun main() {
val baseDir: String? = ""
val networkParameters: String? = ""
if (baseDir != null) {
if (networkParameters != null) {
Unit
} else if (true){
return
} else {
return
}
} else {
return
}
<!DEBUG_INFO_SMARTCAST!>networkParameters<!>.length // unsafe call
}
@@ -0,0 +1,3 @@
package
public fun main(): kotlin.Unit
+1 -1
View File
@@ -1,5 +1,5 @@
// !WITH_NEW_INFERENCE
val test: Int = <!NI;TYPE_MISMATCH, NI;TYPE_MISMATCH, NI;TYPE_MISMATCH!>if (true) {
val test: Int = <!NI;TYPE_MISMATCH, NI;TYPE_MISMATCH!>if (true) {
when (2) {
1 -> 1
else -> <!OI;NULL_FOR_NONNULL_TYPE!>null<!>
@@ -10589,6 +10589,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/inference/completion/kt33166.kt");
}
@TestMetadata("lambdaWithVariableAndNothing.kt")
public void testLambdaWithVariableAndNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/lambdaWithVariableAndNothing.kt");
}
@TestMetadata("partialForIlt.kt")
public void testPartialForIlt() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
@@ -17858,6 +17863,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
runTest("compiler/testData/diagnostics/tests/regressions/kt3535.kt");
}
@TestMetadata("kt35668.kt")
public void testKt35668() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt35668.kt");
}
@TestMetadata("kt3647.kt")
public void testKt3647() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt3647.kt");
@@ -10584,6 +10584,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/inference/completion/kt33166.kt");
}
@TestMetadata("lambdaWithVariableAndNothing.kt")
public void testLambdaWithVariableAndNothing() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/lambdaWithVariableAndNothing.kt");
}
@TestMetadata("partialForIlt.kt")
public void testPartialForIlt() throws Exception {
runTest("compiler/testData/diagnostics/tests/inference/completion/partialForIlt.kt");
@@ -17848,6 +17853,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/regressions/kt3535.kt");
}
@TestMetadata("kt35668.kt")
public void testKt35668() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt35668.kt");
}
@TestMetadata("kt3647.kt")
public void testKt3647() throws Exception {
runTest("compiler/testData/diagnostics/tests/regressions/kt3647.kt");