FIR: Report INFERENCE_NO_INFORMATION_FOR_PARAMETER diagnostic

This commit is contained in:
Denis.Zharkov
2021-05-25 10:33:46 +03:00
committed by TeamCityServer
parent 6e901e3785
commit c420957eac
34 changed files with 143 additions and 236 deletions
@@ -341,6 +341,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val MANY_LAMBDA_EXPRESSION_ARGUMENTS by error<KtValueArgument>()
val NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER by error<KtElement> {
parameter<String>("name")
}
}
val AMBIGUITY by object : DiagnosticGroup("Ambiguity") {
@@ -256,6 +256,7 @@ object FirErrors {
val ASSIGNMENT_TYPE_MISMATCH by error2<KtExpression, ConeKotlinType, ConeKotlinType>()
val RESULT_TYPE_MISMATCH by error2<KtExpression, ConeKotlinType, ConeKotlinType>()
val MANY_LAMBDA_EXPRESSION_ARGUMENTS by error0<KtValueArgument>()
val NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER by error1<KtElement, String>()
// Ambiguity
val OVERLOAD_RESOLUTION_AMBIGUITY by error1<PsiElement, Collection<AbstractFirBasedSymbol<*>>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
@@ -15,6 +15,8 @@ import org.jetbrains.kotlin.fir.declarations.isOperator
import org.jetbrains.kotlin.fir.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.calls.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeParameterBasedTypeVariable
import org.jetbrains.kotlin.fir.resolve.inference.ConeTypeVariableForLambdaReturnType
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeArgumentConstraintPosition
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExpectedTypeConstraintPosition
import org.jetbrains.kotlin.fir.resolve.inference.model.ConeExplicitTypeParameterConstraintPosition
@@ -191,6 +193,7 @@ private fun mapSystemHasContradictionError(
qualifiedAccessSource,
diagnostic.candidate.callInfo.session.typeContext,
errorsToIgnore,
diagnostic.candidate,
)
)
}
@@ -202,6 +205,7 @@ private fun mapSystemHasContradictionError(
is NewConstraintError -> "NewConstraintError at ${it.position}: ${it.lowerType} <!: ${it.upperType}"
// Error should be reported on the error type itself
is ConstrainingTypeIsError -> return@firstNotNullOfOrNull null
is NotEnoughInformationForTypeParameter<*> -> return@firstNotNullOfOrNull null
else -> "Inference error: ${it::class.simpleName}"
}
@@ -223,6 +227,7 @@ private fun ConstraintSystemError.toDiagnostic(
qualifiedAccessSource: FirSourceElement?,
typeContext: ConeTypeContext,
errorsToIgnore: MutableSet<ConstraintSystemError>,
candidate: Candidate,
): FirDiagnostic<FirSourceElement>? {
return when (this) {
is NewConstraintError -> {
@@ -270,6 +275,25 @@ private fun ConstraintSystemError.toDiagnostic(
else -> null
}
}
is NotEnoughInformationForTypeParameter<*> -> {
val isDiagnosticRedundant = candidate.system.errors.any { otherError ->
(otherError is ConstrainingTypeIsError && otherError.typeVariable == this.typeVariable)
|| otherError is NewConstraintError
}
if (isDiagnosticRedundant) return null
val typeVariableName = when (val typeVariable = this.typeVariable) {
is ConeTypeParameterBasedTypeVariable -> typeVariable.typeParameterSymbol.name.asString()
is ConeTypeVariableForLambdaReturnType -> "return type of lambda"
else -> error("Unsupported type variable: $typeVariable")
}
FirErrors.NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER.on(
source,
typeVariableName,
)
}
else -> null
}
}
@@ -87,7 +87,7 @@ typealias ConeKotlinErrorType = ConeClassErrorType
class ConeClassLikeErrorLookupTag(override val classId: ClassId) : ConeClassLikeLookupTag()
class ConeClassErrorType(val diagnostic: ConeDiagnostic) : ConeClassLikeType() {
class ConeClassErrorType(val diagnostic: ConeDiagnostic, val isUninferredParameter: Boolean = false) : ConeClassLikeType() {
override val lookupTag: ConeClassLikeLookupTag
get() = ConeClassLikeErrorLookupTag(ClassId.fromString("<error>"))
@@ -99,8 +99,9 @@ object ConeConstraintSystemUtilContext : ConstraintSystemUtilContext {
argument: PostponedAtomWithRevisableExpectedType,
index: Int
): TypeVariableMarker {
return ConeTypeVariableForPostponedAtom(
PostponedArgumentInputTypesResolver.TYPE_VARIABLE_NAME_PREFIX_FOR_LAMBDA_PARAMETER_TYPE + index
return ConeTypeVariableForLambdaParameterType(
PostponedArgumentInputTypesResolver.TYPE_VARIABLE_NAME_PREFIX_FOR_LAMBDA_PARAMETER_TYPE + index,
index
)
}
@@ -5,6 +5,9 @@
package org.jetbrains.kotlin.fir.resolve.inference
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.calls.Candidate
@@ -15,10 +18,12 @@ import org.jetbrains.kotlin.fir.returnExpressions
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.resolve.calls.inference.components.*
import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl
import org.jetbrains.kotlin.resolve.calls.inference.model.NotEnoughInformationForTypeParameter
import org.jetbrains.kotlin.resolve.calls.inference.model.VariableWithConstraints
import org.jetbrains.kotlin.resolve.calls.model.PostponedAtomWithRevisableExpectedType
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
import org.jetbrains.kotlin.types.model.TypeVariableMarker
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -219,19 +224,83 @@ class ConstraintSystemCompleter(private val components: BodyResolveComponents) {
fixVariable(asConstraintSystemCompletionContext(), topLevelType, variableWithConstraints, postponedArguments)
return true
} else {
// TODO("Not enough information for parameter")
fixVariable(
asConstraintSystemCompletionContext(),
topLevelType,
variableWithConstraints,
postponedArguments
) // means Nothing/Any instead of Error type
processVariableWhenNotEnoughInformation(this, variableWithConstraints, topLevelAtoms)
}
}
return false
}
private fun processVariableWhenNotEnoughInformation(
c: ConstraintSystemCompletionContext,
variableWithConstraints: VariableWithConstraints,
topLevelAtoms: List<FirStatement>,
) {
val typeVariable = variableWithConstraints.typeVariable
val resolvedAtom =
findResolvedAtomBy(typeVariable, topLevelAtoms) ?: topLevelAtoms.firstOrNull()
if (resolvedAtom != null) {
c.addError(NotEnoughInformationForTypeParameter(typeVariable, resolvedAtom))
}
val resultErrorType = when (typeVariable) {
is ConeTypeParameterBasedTypeVariable ->
createCannotInferErrorType(
"Cannot infer argument for type parameter ${typeVariable.typeParameterSymbol.name}",
isUninferredParameter = true,
)
is ConeTypeVariableForLambdaParameterType -> createCannotInferErrorType("Cannot infer lambda parameter type")
else -> createCannotInferErrorType("Cannot infer type variable $typeVariable")
}
c.fixVariable(typeVariable, resultErrorType, ConeFixVariableConstraintPosition(typeVariable))
}
private fun createCannotInferErrorType(message: String, isUninferredParameter: Boolean = false) =
ConeClassErrorType(
ConeSimpleDiagnostic(
message,
DiagnosticKind.CannotInferParameterType,
),
isUninferredParameter,
)
private fun findResolvedAtomBy(
typeVariable: TypeVariableMarker,
topLevelAtoms: List<FirStatement>
): FirStatement? {
fun FirStatement.findFirstAtomContainingVariable(): FirStatement? {
var result: FirStatement? = null
fun suggestElement(element: FirElement) {
if (result == null && element is FirStatement) {
result = element
}
}
this@findFirstAtomContainingVariable.processAllContainingCallCandidates(processBlocks = true) { candidate ->
if (typeVariable in candidate.freshVariables) {
suggestElement(candidate.callInfo.callSite)
}
for (postponedAtom in candidate.postponedAtoms) {
if (postponedAtom is ResolvedLambdaAtom) {
if (postponedAtom.typeVariableForLambdaReturnType == typeVariable) {
suggestElement(postponedAtom.atom)
}
}
}
}
return result
}
return topLevelAtoms.firstNotNullOfOrNull(FirStatement::findFirstAtomContainingVariable)
}
private fun analyzeRemainingNotAnalyzedPostponedArgument(
postponedArguments: List<PostponedResolvedAtom>,
analyze: (PostponedResolvedAtom) -> Unit
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.types.model.KotlinTypeMarker
class ConeTypeVariableForLambdaReturnType(val argument: FirAnonymousFunction, name: String) : ConeTypeVariable(name)
class ConeTypeVariableForPostponedAtom(name: String) : ConeTypeVariable(name)
class ConeTypeVariableForLambdaParameterType(name: String, val index: Int) : ConeTypeVariable(name)
// -------------------------- Atoms --------------------------
@@ -91,7 +91,7 @@ interface ConeTypeContext : TypeSystemContext, TypeSystemOptimizationContext, Ty
override fun KotlinTypeMarker.isUninferredParameter(): Boolean {
assert(this is ConeKotlinType)
return false // TODO
return this is ConeClassErrorType && this.isUninferredParameter
}
override fun FlexibleTypeMarker.asDynamicType(): DynamicTypeMarker? {
@@ -104,7 +104,7 @@ class CapturedTypeFromSubtyping(
val position: ConstraintPosition
) : ConstraintSystemError(INAPPLICABLE)
abstract class NotEnoughInformationForTypeParameter<T>(
open class NotEnoughInformationForTypeParameter<T>(
val typeVariable: TypeVariableMarker,
val resolvedAtom: T
) : ConstraintSystemError(INAPPLICABLE)
@@ -1,11 +0,0 @@
fun <T: Any> fooT22() : T? {
return null
}
fun foo1() {
fooT22()
}
val n : Nothing = null.sure()
fun <T : Any> T?.sure() : T = this!!
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
fun <T: Any> fooT22() : T? {
return null
}
@@ -1,14 +0,0 @@
package noInformationForParameter
//+JDK
import java.util.*
fun test() {
val n = newList()
val n1 : List<String> = newList()
}
fun <S> newList() : ArrayList<S> {
return ArrayList<S>()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
package noInformationForParameter
//+JDK
@@ -1,11 +0,0 @@
// !DIAGNOSTICS: -UNREACHABLE_CODE
//KT-2445 Calling method with function with generic parameter causes compile-time exception
package a
fun main() {
test {
}
}
fun <R> test(callback: (R) -> Unit):Unit = callback(null!!)
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNREACHABLE_CODE
//KT-2445 Calling method with function with generic parameter causes compile-time exception
package a
@@ -1,10 +0,0 @@
//KT-832 Provide better diagnostics when type inference fails for an expression that returns a function
package a
fun <T> fooT2() : (t : T) -> T {
return {it}
}
fun test() {
fooT2()(1) // here 1 should not be marked with an error
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
//KT-832 Provide better diagnostics when type inference fails for an expression that returns a function
package a
@@ -1,6 +0,0 @@
// NI_EXPECTED_FILE
val x get() = foo()
val y get() = bar()
fun <E> foo(): E = null!!
fun <E> bar(): List<E> = null!!
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// NI_EXPECTED_FILE
val x get() = <!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>foo<!>()
val y get() = <!NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER!>bar<!>()
@@ -1,32 +0,0 @@
// KT-353 Generic type argument inference sometimes doesn't work
interface A {
fun <T> gen() : T
}
fun foo(a: A) {
val g : () -> Unit = {
a.gen() //it works: Unit is derived
}
val u: Unit = a.gen() // Unit should be inferred
if (true) {
a.gen() // Shouldn't work: no info for inference
}
val b : () -> Unit = {
if (true) {
a.gen() // unit can be inferred
}
else {
Unit
}
}
val f : () -> Int = {
a.gen() //type mismatch, but Int can be derived
}
a.gen() // Shouldn't work: no info for inference
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// KT-353 Generic type argument inference sometimes doesn't work
interface A {
@@ -1,51 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// NI_EXPECTED_FILE
@file:OptIn(ExperimentalTypeInference::class)
import kotlin.experimental.ExperimentalTypeInference
interface Base<K>
interface Controller<T> : Base<T> {
suspend fun yield(t: T) {}
}
interface SpecificController<T> : Base<String> {
suspend fun yield(t: T) {}
}
fun <S> generate(@BuilderInference g: suspend Controller<S>.() -> Unit): S = TODO()
fun <S> generateSpecific(@BuilderInference g: suspend SpecificController<S>.() -> Unit): S = TODO()
fun Base<*>.starBase() {}
fun Base<String>.stringBase() {}
val test1 = generate {
starBase()
yield("foo")
}
val test2 = generate {
starBase()
}
val test3 = generate {
yield("bar")
stringBase()
}
val test4 = generateSpecific {
yield(42)
starBase()
}
val test5 = generateSpecific {
yield(42)
stringBase()
}
val test6 = generateSpecific {
stringBase()
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// NI_EXPECTED_FILE
@@ -1,23 +0,0 @@
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE
// NI_EXPECTED_FILE
@file:OptIn(ExperimentalTypeInference::class)
import kotlin.experimental.ExperimentalTypeInference
class GenericController<T> {
suspend fun yield(t: T) {}
}
fun <S> generate(@BuilderInference g: suspend GenericController<S>.() -> Unit): List<S> = TODO()
@BuilderInference
suspend fun <S> GenericController<List<S>>.yieldGenerate(g: suspend GenericController<S>.() -> Unit): Unit = TODO()
val test1 = generate {
// TODO: KT-15185
yieldGenerate {
yield(4)
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE
// NI_EXPECTED_FILE
@@ -1,12 +0,0 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
// NI_EXPECTED_FILE
class Controller<T : Number> {
suspend fun yield(t: T) {}
}
fun <S : Number> generate(g: suspend Controller<S>.() -> Unit): S = TODO()
val test = generate {
yield(<!ARGUMENT_TYPE_MISMATCH!>"foo"<!>)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_PARAMETER
// NI_EXPECTED_FILE
@@ -1,20 +0,0 @@
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_ANONYMOUS_PARAMETER -UNUSED_VARIABLE
// NI_EXPECTED_FILE
class GenericController<T> {
suspend fun yield(t: T) {}
}
fun <S> generate(g: suspend GenericController<S>.(S) -> Unit): S = TODO()
val test1 = generate {
yield(<!ARGUMENT_TYPE_MISMATCH!>4<!>)
}
val test2 = generate<Int> {
yield(4)
}
val test3 = generate { bar: Int ->
yield(4)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_ANONYMOUS_PARAMETER -UNUSED_VARIABLE
// NI_EXPECTED_FILE
@@ -1,34 +0,0 @@
// !LANGUAGE: +NewInference
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE
import kotlin.experimental.ExperimentalTypeInference
fun test1() {
sequence {
val a: Array<Int> = arrayOf(1, 2, 3)
val b = arrayOf(1, 2, 3)
}
}
fun test2() = sequence { arrayOf(1, 2, 3) }
class Foo<T>
fun <T> f1(f: Foo<T>.() -> Unit) {}
@OptIn(ExperimentalTypeInference::class)
fun <T> f2(@BuilderInference f: Foo<T>.() -> Unit) {
}
fun test3() {
f1 {
val a: Array<Int> = arrayOf(1, 2, 3)
}
f2 {
val a: Array<Int> = arrayOf(1, 2, 3)
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: +NewInference
// !USE_EXPERIMENTAL: kotlin.RequiresOptIn
// !DIAGNOSTICS: -UNUSED_EXPRESSION -UNUSED_PARAMETER -UNUSED_VARIABLE
@@ -1082,6 +1082,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.NEW_INFERENCE_NO_INFORMATION_FOR_PARAMETER) { firDiagnostic ->
NewInferenceNoInformationForParameterImpl(
firDiagnostic.a,
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.OVERLOAD_RESOLUTION_AMBIGUITY) { firDiagnostic ->
OverloadResolutionAmbiguityImpl(
firDiagnostic.a.map { abstractFirBasedSymbol ->
@@ -774,6 +774,11 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = ManyLambdaExpressionArguments::class
}
abstract class NewInferenceNoInformationForParameter : KtFirDiagnostic<KtElement>() {
override val diagnosticClass get() = NewInferenceNoInformationForParameter::class
abstract val name: String
}
abstract class OverloadResolutionAmbiguity : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = OverloadResolutionAmbiguity::class
abstract val candidates: List<KtSymbol>
@@ -1246,6 +1246,14 @@ internal class ManyLambdaExpressionArgumentsImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class NewInferenceNoInformationForParameterImpl(
override val name: String,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.NewInferenceNoInformationForParameter(), KtAbstractFirDiagnostic<KtElement> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class OverloadResolutionAmbiguityImpl(
override val candidates: List<KtSymbol>,
firDiagnostic: FirPsiDiagnostic<*>,