K2: Avoid using Nothing? as inference result in the majority of cases

Namely, do not choose `Nothing?` result type when fixing a variable
that has other constraints besides the ones that came from
the relevant type parameter's upper bounds.

See more details in KT-55691.

In K1, the case from specialCallWithMaterializeAndExpectedType.kt
was working (inferred to String?) just because the branches
were analyzed independently with `String?` expected type.

This change became necessary after the previous commit when we united
inference subsystems for if/when branches (see motivation there).

NB: For K1, the behavior is left the same, but the code
was refactored a bit.

^KT-55691 Fixed
^KT-56448 Fixed
This commit is contained in:
Denis.Zharkov
2022-12-02 19:28:18 +01:00
committed by Space Team
parent f12a4e08cf
commit 2bafcddf7a
18 changed files with 289 additions and 68 deletions
@@ -0,0 +1,43 @@
// SKIP_TXT
// WITH_STDLIB
// ISSUE: KT-56448
// FILE: SomeJavaClass.java
public class SomeJavaClass {
public static String getString() {
return "";
}
}
// FILE: main.kt
import kotlin.reflect.KProperty1
interface XdEntity
class XdIssue : XdEntity {
val isRemoved: Boolean = true
var votes: Int = 0
}
fun getNullableIssue(): XdIssue? = null
fun <T : XdEntity> CharSequence.toXd(): T = null!!
fun XdIssue.duplicatesRootSearch(): XdIssue = this
fun <T : XdEntity, R : Comparable<*>?> T.getOldValue(property: KProperty1<T, R>): R? = null
internal fun updateVotesForDuplicates(issue: XdIssue) {
val toRecount = HashSet<XdIssue>()
var oldDup = getNullableIssue()
if (oldDup != null) {
oldDup = try {
<!DEBUG_INFO_EXPRESSION_TYPE("XdIssue")!>SomeJavaClass.getString().toXd()<!>
} catch (_: Exception) {
null
}
}
val newDup = getNullableIssue()
if (oldDup != null && !oldDup.isRemoved) {
toRecount.add(oldDup.duplicatesRootSearch())
}
}
@@ -0,0 +1,43 @@
// SKIP_TXT
// WITH_STDLIB
// ISSUE: KT-56448
// FILE: SomeJavaClass.java
public class SomeJavaClass {
public static String getString() {
return "";
}
}
// FILE: main.kt
import kotlin.reflect.KProperty1
interface XdEntity
class XdIssue : XdEntity {
val isRemoved: Boolean = true
var votes: Int = 0
}
fun getNullableIssue(): XdIssue? = null
fun <T : XdEntity> CharSequence.toXd(): T = null!!
fun XdIssue.duplicatesRootSearch(): XdIssue = this
fun <T : XdEntity, R : Comparable<*>?> T.getOldValue(property: KProperty1<T, R>): R? = null
internal fun updateVotesForDuplicates(issue: XdIssue) {
val toRecount = HashSet<XdIssue>()
var oldDup = getNullableIssue()
if (oldDup != null) {
oldDup = try {
<!DEBUG_INFO_EXPRESSION_TYPE("XdIssue")!>SomeJavaClass.getString().toXd()<!>
} catch (_: Exception) {
null
}
}
val newDup = getNullableIssue()
if (oldDup != null && !<!DEBUG_INFO_SMARTCAST!>oldDup<!>.isRemoved) {
toRecount.add(<!DEBUG_INFO_SMARTCAST!>oldDup<!>.duplicatesRootSearch())
}
}
@@ -0,0 +1,13 @@
// SKIP_TXT
// ISSUE: KT-55691
fun <T> materialize(): T = TODO()
fun <S> select(x: S, y: S): S = x
fun main() {
// materialize's type argument is inferred to `Nothing?` in K1 and `String?` in K2
val x: String? = select(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String?")!>materialize()<!>, null)
// materialize's type argument is inferred to `Nothing?` both in K1 and K2
select(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!>materialize()<!>, null)
}
@@ -0,0 +1,13 @@
// SKIP_TXT
// ISSUE: KT-55691
fun <T> materialize(): T = TODO()
fun <S> select(x: S, y: S): S = x
fun main() {
// materialize's type argument is inferred to `Nothing?` in K1 and `String?` in K2
val x: String? = select(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!><!IMPLICIT_NOTHING_TYPE_ARGUMENT_AGAINST_NOT_NOTHING_EXPECTED_TYPE!>materialize<!>()<!>, null)
// materialize's type argument is inferred to `Nothing?` both in K1 and K2
select(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!>materialize()<!>, null)
}
@@ -1,7 +0,0 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun foo() {
val s: String? = if (true) <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!>materialize()<!> else null
}
fun <K> materialize(): K = TODO()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_VARIABLE
fun foo() {
@@ -0,0 +1,15 @@
// FIR_IDENTICAL
// SKIP_TXT
class Out<out T : CharSequence?>(val t: T)
fun foo() {
// We have two constraints here:
// Nothing? <: T (from argument `null` type)
// T <: CharSequence?
// And we fix T to `Nothing?`, because it's still more preferrable than constraint from the upper bound
val x1 = Out(null)
bar(<!DEBUG_INFO_EXPRESSION_TYPE("Out<kotlin.Nothing?>")!>x1<!>)
}
fun bar(w: Out<String?>) {}
@@ -8,7 +8,7 @@ fun test() {
val map: Map<String, Int> = mapOf("x" to 1)
val r1 = map.getOrDefault_Exact("y", null)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Nothing?")!>r1<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>r1<!>
val r2 = map.getOrDefault_Exact("y", null as Int?)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int?")!>r2<!>