[FIR] Report empty intersection on responsible call
When reporting INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION, search for a call to a declaration with the type parameter that got inferred into an empty intersection inside the expression. #KT-56377 Fixed
This commit is contained in:
committed by
Space Team
parent
3f58782273
commit
585d4b3098
+36
-2
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.KtSourceElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
|
||||
import org.jetbrains.kotlin.fir.analysis.getChild
|
||||
@@ -20,6 +21,8 @@ import org.jetbrains.kotlin.fir.declarations.utils.isInner
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isOperator
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||
import org.jetbrains.kotlin.fir.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.*
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeParameterBasedTypeVariable
|
||||
@@ -33,6 +36,7 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.SpecialNames
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.model.*
|
||||
@@ -461,10 +465,13 @@ private fun ConstraintSystemError.toDiagnostic(
|
||||
}
|
||||
|
||||
is InferredEmptyIntersection -> {
|
||||
val typeVariable = typeVariable as ConeTypeVariable
|
||||
val narrowedSource = findInferredEmptyIntersectionNarrowedSource(typeVariable, candidate)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
reportInferredIntoEmptyIntersection(
|
||||
source,
|
||||
typeVariable as ConeTypeVariable,
|
||||
narrowedSource ?: source,
|
||||
typeVariable,
|
||||
incompatibleTypes as Collection<ConeKotlinType>,
|
||||
causingTypes as Collection<ConeKotlinType>,
|
||||
kind,
|
||||
@@ -483,6 +490,33 @@ private fun ConstraintSystemError.toDiagnostic(
|
||||
}
|
||||
}
|
||||
|
||||
private fun findInferredEmptyIntersectionNarrowedSource(
|
||||
typeVariable: ConeTypeVariable,
|
||||
candidate: AbstractCandidate,
|
||||
): KtSourceElement? {
|
||||
if (typeVariable !is ConeTypeParameterBasedTypeVariable) return null
|
||||
|
||||
var narrowedSource: KtSourceElement? = null
|
||||
|
||||
candidate.callInfo.callSite.accept(object : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
if (narrowedSource != null) return
|
||||
|
||||
if (element is FirQualifiedAccessExpression) {
|
||||
val symbol = element.calleeReference.toResolvedCallableSymbol()
|
||||
if (symbol != null && symbol.typeParameterSymbols.contains(typeVariable.typeParameterSymbol)) {
|
||||
narrowedSource = element.calleeReference.source
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
}, null)
|
||||
|
||||
return narrowedSource
|
||||
}
|
||||
|
||||
private fun reportInferredIntoEmptyIntersection(
|
||||
source: KtSourceElement,
|
||||
typeVariable: ConeTypeVariable,
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
interface I
|
||||
open class C
|
||||
|
||||
fun completed(): String = "..."
|
||||
|
||||
fun <T> incomplete(): T = null!!
|
||||
|
||||
fun <T : I> incompatibleI(): T = null!!
|
||||
fun <T : C> incompatibleC(): T = null!!
|
||||
|
||||
val p = false
|
||||
|
||||
fun expectUnit(x: Unit) = x
|
||||
|
||||
fun test1() = run {
|
||||
if (p) return@run
|
||||
completed() // ok, not returned
|
||||
}
|
||||
|
||||
fun test2() = run {
|
||||
if (p) return@run
|
||||
incomplete() // ? either uninferred T or Unit
|
||||
}
|
||||
|
||||
fun test3() = run {
|
||||
if (p) return@run
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION!>incompatibleI<!>() // ? either uninferred T or error (Unit </: I)
|
||||
}
|
||||
|
||||
fun test4() = run {
|
||||
if (p) return@run
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>incompatibleC<!>() // ? either uninferred T or error (Unit </: C)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
// all ok
|
||||
expectUnit(test1())
|
||||
expectUnit(test2())
|
||||
expectUnit(test3())
|
||||
expectUnit(test4())
|
||||
}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
interface I
|
||||
open class C
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// RENDER_DIAGNOSTICS_FULL_TEXT
|
||||
open class Foo
|
||||
inline fun <reified T : Foo> g(): T? = null
|
||||
|
||||
inline fun <R> f(block: ()->R?): R? {
|
||||
return block()
|
||||
}
|
||||
|
||||
fun main() {
|
||||
f<Int> { <!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>g<!>() }
|
||||
}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// RENDER_DIAGNOSTICS_FULL_TEXT
|
||||
open class Foo
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
fun <T : <!FINAL_UPPER_BOUND!>String<!>> g(): T? = null
|
||||
|
||||
fun <R> f(block: () -> R?): R? = block()
|
||||
|
||||
fun main() {
|
||||
f<Int> { <!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>g<!>() /* OK, g() is inferred into {Int & String}? */ }
|
||||
}
|
||||
Vendored
-1
@@ -1,4 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
fun <T : <!FINAL_UPPER_BOUND!>String<!>> g(): T? = null
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ fun <T: A> emptyNullableListOfA(): List<T>? = null
|
||||
|
||||
fun testExclExcl() {
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION("T; a/A, kotlin/Int; final class and interface")!>doList<!>(emptyNullableListOfA()!!) //should be an error here
|
||||
val l: List<Int> = <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<a.A & kotlin.Int>")!><!INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION!>id<!>(emptyNullableListOfA()!!)<!>
|
||||
val l: List<Int> = <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.collections.List<a.A & kotlin.Int>")!>id(<!INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION!>emptyNullableListOfA<!>()!!)<!>
|
||||
|
||||
doList(strangeNullableList { doInt(it) }!!) //lambda should be analyzed (at completion phase)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// !LANGUAGE: +ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// FILE: ObjectNode.java
|
||||
|
||||
public interface ObjectNode {
|
||||
<T extends JsonNode> T set(String fieldName, JsonNode value);
|
||||
}
|
||||
|
||||
// FILE: JsonNode.java
|
||||
|
||||
public class JsonNode
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
interface JsonObject
|
||||
class SomeJsonObject() : JsonObject
|
||||
|
||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
select(
|
||||
node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>set<!>(this, null),
|
||||
Unit,
|
||||
)
|
||||
|
||||
if (value == null) node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>set<!>(this, null)
|
||||
else if (value is SomeJsonObject) Unit
|
||||
|
||||
when (value) {
|
||||
null -> node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>set<!>(this, null)
|
||||
is SomeJsonObject -> Unit
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
fun TODO(): Nothing = null!!
|
||||
fun <K> select(vararg values: K): K = values[0]
|
||||
@@ -1,4 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: +ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// FILE: ObjectNode.java
|
||||
|
||||
@@ -16,6 +15,14 @@ interface JsonObject
|
||||
class SomeJsonObject() : JsonObject
|
||||
|
||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>select<!>(
|
||||
node.set(this, null),
|
||||
Unit,
|
||||
)
|
||||
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>if (value == null) node.set(this, null)
|
||||
else if (value is SomeJsonObject) Unit<!>
|
||||
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>when (value) {
|
||||
null -> node.set(this, null)
|
||||
is SomeJsonObject -> Unit
|
||||
@@ -24,3 +31,4 @@ fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
}
|
||||
|
||||
fun TODO(): Nothing = null!!
|
||||
fun <K> select(vararg values: K): K = values[0]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package
|
||||
|
||||
public fun TODO(): kotlin.Nothing
|
||||
public fun </*0*/ K> select(/*0*/ vararg values: K /*kotlin.Array<out K>*/): K
|
||||
public fun kotlin.String.put(/*0*/ value: JsonObject?, /*1*/ node: ObjectNode): kotlin.Unit
|
||||
|
||||
public open class JsonNode {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// FILE: ObjectNode.java
|
||||
|
||||
public interface ObjectNode {
|
||||
<T extends JsonNode> T set(String fieldName, JsonNode value);
|
||||
}
|
||||
|
||||
// FILE: JsonNode.java
|
||||
|
||||
public class JsonNode
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
interface JsonObject
|
||||
class SomeJsonObject() : JsonObject
|
||||
|
||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
select(
|
||||
node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>set<!>(this, null),
|
||||
Unit,
|
||||
)
|
||||
|
||||
if (value == null) node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>set<!>(this, null)
|
||||
else if (value is SomeJsonObject) Unit
|
||||
|
||||
when (value) {
|
||||
null -> node.<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>set<!>(this, null)
|
||||
is SomeJsonObject -> Unit
|
||||
else -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
fun TODO(): Nothing = null!!
|
||||
fun <K> select(vararg values: K): K = values[0]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// FIR_IDENTICAL
|
||||
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||
// FILE: ObjectNode.java
|
||||
|
||||
@@ -16,6 +15,14 @@ interface JsonObject
|
||||
class SomeJsonObject() : JsonObject
|
||||
|
||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>select<!>(
|
||||
node.set(this, null),
|
||||
Unit,
|
||||
)
|
||||
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>if (value == null) node.set(this, null)
|
||||
else if (value is SomeJsonObject) Unit<!>
|
||||
|
||||
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>when (value) {
|
||||
null -> node.set(this, null)
|
||||
is SomeJsonObject -> Unit
|
||||
@@ -24,3 +31,5 @@ fun String.put(value: JsonObject?, node: ObjectNode) {
|
||||
}
|
||||
|
||||
fun TODO(): Nothing = null!!
|
||||
fun <K> select(vararg values: K): K = values[0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user