[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.KtSourceElement
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.diagnostics.*
|
import org.jetbrains.kotlin.diagnostics.*
|
||||||
|
import org.jetbrains.kotlin.fir.FirElement
|
||||||
import org.jetbrains.kotlin.fir.FirSession
|
import org.jetbrains.kotlin.fir.FirSession
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
|
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isLocalMember
|
||||||
import org.jetbrains.kotlin.fir.analysis.getChild
|
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.isOperator
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||||
import org.jetbrains.kotlin.fir.diagnostics.*
|
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.calls.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
|
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
|
||||||
import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeParameterBasedTypeVariable
|
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.FirClassLikeSymbol
|
||||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||||
import org.jetbrains.kotlin.fir.types.*
|
import org.jetbrains.kotlin.fir.types.*
|
||||||
|
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.name.SpecialNames
|
import org.jetbrains.kotlin.name.SpecialNames
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.*
|
import org.jetbrains.kotlin.resolve.calls.inference.model.*
|
||||||
@@ -461,10 +465,13 @@ private fun ConstraintSystemError.toDiagnostic(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is InferredEmptyIntersection -> {
|
is InferredEmptyIntersection -> {
|
||||||
|
val typeVariable = typeVariable as ConeTypeVariable
|
||||||
|
val narrowedSource = findInferredEmptyIntersectionNarrowedSource(typeVariable, candidate)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
reportInferredIntoEmptyIntersection(
|
reportInferredIntoEmptyIntersection(
|
||||||
source,
|
narrowedSource ?: source,
|
||||||
typeVariable as ConeTypeVariable,
|
typeVariable,
|
||||||
incompatibleTypes as Collection<ConeKotlinType>,
|
incompatibleTypes as Collection<ConeKotlinType>,
|
||||||
causingTypes as Collection<ConeKotlinType>,
|
causingTypes as Collection<ConeKotlinType>,
|
||||||
kind,
|
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(
|
private fun reportInferredIntoEmptyIntersection(
|
||||||
source: KtSourceElement,
|
source: KtSourceElement,
|
||||||
typeVariable: ConeTypeVariable,
|
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
|
interface I
|
||||||
open class C
|
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
|
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||||
// RENDER_DIAGNOSTICS_FULL_TEXT
|
// RENDER_DIAGNOSTICS_FULL_TEXT
|
||||||
open class Foo
|
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
|
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||||
fun <T : <!FINAL_UPPER_BOUND!>String<!>> g(): T? = null
|
fun <T : <!FINAL_UPPER_BOUND!>String<!>> g(): T? = null
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ fun <T: A> emptyNullableListOfA(): List<T>? = null
|
|||||||
|
|
||||||
fun testExclExcl() {
|
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
|
<!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)
|
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
|
// !LANGUAGE: +ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||||
// FILE: ObjectNode.java
|
// FILE: ObjectNode.java
|
||||||
|
|
||||||
@@ -16,6 +15,14 @@ interface JsonObject
|
|||||||
class SomeJsonObject() : JsonObject
|
class SomeJsonObject() : JsonObject
|
||||||
|
|
||||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
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) {
|
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_ERROR!>when (value) {
|
||||||
null -> node.set(this, null)
|
null -> node.set(this, null)
|
||||||
is SomeJsonObject -> Unit
|
is SomeJsonObject -> Unit
|
||||||
@@ -24,3 +31,4 @@ fun String.put(value: JsonObject?, node: ObjectNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun TODO(): Nothing = null!!
|
fun TODO(): Nothing = null!!
|
||||||
|
fun <K> select(vararg values: K): K = values[0]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package
|
package
|
||||||
|
|
||||||
public fun TODO(): kotlin.Nothing
|
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 fun kotlin.String.put(/*0*/ value: JsonObject?, /*1*/ node: ObjectNode): kotlin.Unit
|
||||||
|
|
||||||
public open class JsonNode {
|
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
|
// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection
|
||||||
// FILE: ObjectNode.java
|
// FILE: ObjectNode.java
|
||||||
|
|
||||||
@@ -16,6 +15,14 @@ interface JsonObject
|
|||||||
class SomeJsonObject() : JsonObject
|
class SomeJsonObject() : JsonObject
|
||||||
|
|
||||||
fun String.put(value: JsonObject?, node: ObjectNode) {
|
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) {
|
<!INFERRED_TYPE_VARIABLE_INTO_EMPTY_INTERSECTION_WARNING!>when (value) {
|
||||||
null -> node.set(this, null)
|
null -> node.set(this, null)
|
||||||
is SomeJsonObject -> Unit
|
is SomeJsonObject -> Unit
|
||||||
@@ -24,3 +31,5 @@ fun String.put(value: JsonObject?, node: ObjectNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun TODO(): Nothing = null!!
|
fun TODO(): Nothing = null!!
|
||||||
|
fun <K> select(vararg values: K): K = values[0]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user