From 585d4b30983f9cb667e1977490fa351768fd5a0e Mon Sep 17 00:00:00 2001 From: Kirill Rakhman Date: Tue, 20 Jun 2023 09:44:48 +0200 Subject: [PATCH] [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 --- .../coneDiagnosticToFirDiagnostic.kt | 38 ++++++++++++++++- .../coercionToUnit/afterBareReturn.fir.kt | 41 +++++++++++++++++++ .../coercionToUnit/afterBareReturn.kt | 1 - .../emptyIntersectionTypes/kt49661.fir.kt | 12 ++++++ .../emptyIntersectionTypes/kt49661.kt | 1 - .../nullableEmptyIntersection.fir.kt | 8 ++++ .../nullableEmptyIntersection.kt | 1 - .../exclExclAsCall.fir.kt | 2 +- .../tests/when/TypeParameterError.fir.kt | 34 +++++++++++++++ .../tests/when/TypeParameterError.kt | 10 ++++- .../tests/when/TypeParameterError.txt | 1 + .../tests/when/TypeParameterWarning.fir.kt | 35 ++++++++++++++++ .../tests/when/TypeParameterWarning.kt | 11 ++++- 13 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/inference/coercionToUnit/afterBareReturn.fir.kt create mode 100644 compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/kt49661.fir.kt create mode 100644 compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.fir.kt create mode 100644 compiler/testData/diagnostics/tests/when/TypeParameterError.fir.kt create mode 100644 compiler/testData/diagnostics/tests/when/TypeParameterWarning.fir.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt index e0ce80ff682..67228f282ad 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/coneDiagnosticToFirDiagnostic.kt @@ -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, causingTypes as Collection, 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, diff --git a/compiler/testData/diagnostics/tests/inference/coercionToUnit/afterBareReturn.fir.kt b/compiler/testData/diagnostics/tests/inference/coercionToUnit/afterBareReturn.fir.kt new file mode 100644 index 00000000000..d36e77a2d5b --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/coercionToUnit/afterBareReturn.fir.kt @@ -0,0 +1,41 @@ +interface I +open class C + +fun completed(): String = "..." + +fun incomplete(): T = null!! + +fun incompatibleI(): T = null!! +fun 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 + incompatibleI() // ? either uninferred T or error (Unit incompatibleC() // ? either uninferred T or error (Unit g(): T? = null + +inline fun f(block: ()->R?): R? { + return block() +} + +fun main() { + f { g() } +} diff --git a/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/kt49661.kt b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/kt49661.kt index 8c898863f34..d660c0aee00 100644 --- a/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/kt49661.kt +++ b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/kt49661.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection // RENDER_DIAGNOSTICS_FULL_TEXT open class Foo diff --git a/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.fir.kt b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.fir.kt new file mode 100644 index 00000000000..b84a8673e4c --- /dev/null +++ b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.fir.kt @@ -0,0 +1,8 @@ +// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection +fun String> g(): T? = null + +fun f(block: () -> R?): R? = block() + +fun main() { + f { g() /* OK, g() is inferred into {Int & String}? */ } +} diff --git a/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.kt b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.kt index fb0f775cbe7..23eec34292b 100644 --- a/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.kt +++ b/compiler/testData/diagnostics/tests/inference/emptyIntersectionTypes/nullableEmptyIntersection.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection fun String> g(): T? = null diff --git a/compiler/testData/diagnostics/tests/resolve/specialConstructions/exclExclAsCall.fir.kt b/compiler/testData/diagnostics/tests/resolve/specialConstructions/exclExclAsCall.fir.kt index 5ffe2b9b5bc..97b79ee6dbd 100644 --- a/compiler/testData/diagnostics/tests/resolve/specialConstructions/exclExclAsCall.fir.kt +++ b/compiler/testData/diagnostics/tests/resolve/specialConstructions/exclExclAsCall.fir.kt @@ -13,7 +13,7 @@ fun emptyNullableListOfA(): List? = null fun testExclExcl() { doList(emptyNullableListOfA()!!) //should be an error here - val l: List = ")!>id(emptyNullableListOfA()!!) + val l: List = ")!>id(emptyNullableListOfA()!!) doList(strangeNullableList { doInt(it) }!!) //lambda should be analyzed (at completion phase) } diff --git a/compiler/testData/diagnostics/tests/when/TypeParameterError.fir.kt b/compiler/testData/diagnostics/tests/when/TypeParameterError.fir.kt new file mode 100644 index 00000000000..c9c43a86856 --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/TypeParameterError.fir.kt @@ -0,0 +1,34 @@ +// !LANGUAGE: +ForbidInferringTypeVariablesIntoEmptyIntersection +// FILE: ObjectNode.java + +public interface ObjectNode { + 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.set(this, null), + Unit, + ) + + if (value == null) node.set(this, null) + else if (value is SomeJsonObject) Unit + + when (value) { + null -> node.set(this, null) + is SomeJsonObject -> Unit + else -> TODO() + } +} + +fun TODO(): Nothing = null!! +fun select(vararg values: K): K = values[0] diff --git a/compiler/testData/diagnostics/tests/when/TypeParameterError.kt b/compiler/testData/diagnostics/tests/when/TypeParameterError.kt index 15168835ca9..35a2a5426c4 100644 --- a/compiler/testData/diagnostics/tests/when/TypeParameterError.kt +++ b/compiler/testData/diagnostics/tests/when/TypeParameterError.kt @@ -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) { + select( + node.set(this, null), + Unit, + ) + + if (value == null) node.set(this, null) + else if (value is SomeJsonObject) Unit + 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 select(vararg values: K): K = values[0] diff --git a/compiler/testData/diagnostics/tests/when/TypeParameterError.txt b/compiler/testData/diagnostics/tests/when/TypeParameterError.txt index 57d76732e0a..cfd7098678f 100644 --- a/compiler/testData/diagnostics/tests/when/TypeParameterError.txt +++ b/compiler/testData/diagnostics/tests/when/TypeParameterError.txt @@ -1,6 +1,7 @@ package public fun TODO(): kotlin.Nothing +public fun select(/*0*/ vararg values: K /*kotlin.Array*/): K public fun kotlin.String.put(/*0*/ value: JsonObject?, /*1*/ node: ObjectNode): kotlin.Unit public open class JsonNode { diff --git a/compiler/testData/diagnostics/tests/when/TypeParameterWarning.fir.kt b/compiler/testData/diagnostics/tests/when/TypeParameterWarning.fir.kt new file mode 100644 index 00000000000..4d26fd87b8a --- /dev/null +++ b/compiler/testData/diagnostics/tests/when/TypeParameterWarning.fir.kt @@ -0,0 +1,35 @@ +// !LANGUAGE: -ForbidInferringTypeVariablesIntoEmptyIntersection +// FILE: ObjectNode.java + +public interface ObjectNode { + 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.set(this, null), + Unit, + ) + + if (value == null) node.set(this, null) + else if (value is SomeJsonObject) Unit + + when (value) { + null -> node.set(this, null) + is SomeJsonObject -> Unit + else -> TODO() + } +} + +fun TODO(): Nothing = null!! +fun select(vararg values: K): K = values[0] + diff --git a/compiler/testData/diagnostics/tests/when/TypeParameterWarning.kt b/compiler/testData/diagnostics/tests/when/TypeParameterWarning.kt index 32f70979499..f0bd760ba87 100644 --- a/compiler/testData/diagnostics/tests/when/TypeParameterWarning.kt +++ b/compiler/testData/diagnostics/tests/when/TypeParameterWarning.kt @@ -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) { + select( + node.set(this, null), + Unit, + ) + + if (value == null) node.set(this, null) + else if (value is SomeJsonObject) Unit + 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 select(vararg values: K): K = values[0] +