FIR checker: report COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH

This commit is contained in:
Jinseong Jeon
2021-04-06 16:45:45 -07:00
committed by Mikhail Glukhikh
parent e511eec90e
commit 87c50235ed
20 changed files with 194 additions and 74 deletions
@@ -6,8 +6,8 @@ fun foo2(block: (C, C) -> Unit) = block(C(0, ""), C(0, ""))
fun test() {
foo1 { (x, y) -> C(x, y) }
foo1 { (x: Int, y: String) -> C(x, y) }
foo1 { (x: String, y: Int) -> <!INAPPLICABLE_CANDIDATE!>C<!>(x, y) }
foo1 { (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>x: String<!>, <!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>y: Int<!>) -> <!INAPPLICABLE_CANDIDATE!>C<!>(x, y) }
foo2 { (x, y), (z, w) -> C(x + z, y + w) }
foo2 { (x: Int, y: String), (z: Int, w: String) -> C(x + z, y + w) }
foo2 { (x: String, y: Int), (z: String, w: Int) -> <!INAPPLICABLE_CANDIDATE!>C<!>(x + z, y + w) }
foo2 { (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>x: String<!>, <!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>y: Int<!>), (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>z: String<!>, <!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>w: Int<!>) -> <!INAPPLICABLE_CANDIDATE!>C<!>(x + z, y + w) }
}
@@ -28811,6 +28811,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/typeParameters/deprecatedSyntax.kt");
}
@Test
@TestMetadata("destructuringDeclarations.kt")
public void testDestructuringDeclarations() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/destructuringDeclarations.kt");
}
@Test
@TestMetadata("dontIntersectUpperBoundWithExpectedType.kt")
public void testDontIntersectUpperBoundWithExpectedType() throws Exception {
@@ -508,12 +508,14 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
parameter<Name>("functionWithAmbiguityName")
parameter<Collection<AbstractFirBasedSymbol<*>>>("candidates")
}
val COMPONENT_FUNCTION_ON_NULLABLE by error<FirSourceElement, KtExpression> {
parameter<Name>("componentFunctionName")
}
// TODO: val COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH by ...
val COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH by error<FirSourceElement, KtExpression> {
parameter<Name>("componentFunctionName")
parameter<ConeKotlinType>("destructingType")
parameter<ConeKotlinType>("expectedType")
}
}
val CONTROL_FLOW by object : DiagnosticGroup("Control flow diagnostics") {
@@ -315,6 +315,7 @@ object FirErrors {
val COMPONENT_FUNCTION_MISSING by error2<FirSourceElement, PsiElement, Name, ConeKotlinType>()
val COMPONENT_FUNCTION_AMBIGUITY by error2<FirSourceElement, PsiElement, Name, Collection<AbstractFirBasedSymbol<*>>>()
val COMPONENT_FUNCTION_ON_NULLABLE by error1<FirSourceElement, KtExpression, Name>()
val COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH by error3<FirSourceElement, KtExpression, Name, ConeKotlinType, ConeKotlinType>()
// Control flow diagnostics
val UNINITIALIZED_VARIABLE by error1<FirSourceElement, KtSimpleNameExpression, FirPropertySymbol>()
@@ -24,9 +24,9 @@ import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguityError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeInapplicableCandidateError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.fir.types.isNullable
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
object FirDestructuringDeclarationChecker : FirPropertyChecker() {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
@@ -44,8 +44,6 @@ object FirDestructuringDeclarationChecker : FirPropertyChecker() {
if (source.elementType != KtNodeTypes.DESTRUCTURING_DECLARATION_ENTRY) return
val componentCall = declaration.initializer as? FirComponentCall ?: return
val reference = componentCall.calleeReference as? FirErrorNamedReference ?: return
val originalExpression = componentCall.explicitReceiverOfQualifiedAccess ?: return
val originalDestructuringDeclaration = originalExpression.resolvedVariable ?: return
val originalDestructuringDeclarationOrInitializer =
@@ -75,39 +73,25 @@ object FirDestructuringDeclarationChecker : FirPropertyChecker() {
else -> null
} ?: return
when (val diagnostic = reference.diagnostic) {
is ConeUnresolvedNameError -> {
reporter.report(
FirErrors.COMPONENT_FUNCTION_MISSING.on(
originalDestructuringDeclarationOrInitializerSource,
diagnostic.name,
originalDestructuringDeclarationType
),
when (val reference = componentCall.calleeReference) {
is FirResolvedNamedReference ->
checkComponentCallReturnType(
originalDestructuringDeclarationOrInitializerSource,
declaration,
componentCall,
originalDestructuringDeclaration,
reference,
reporter,
context
)
}
is ConeAmbiguityError -> {
reporter.report(
FirErrors.COMPONENT_FUNCTION_AMBIGUITY.on(
originalDestructuringDeclarationOrInitializerSource,
diagnostic.name,
diagnostic.candidates
),
is FirErrorNamedReference ->
checkComponentCall(
originalDestructuringDeclarationOrInitializerSource,
originalDestructuringDeclarationType,
reference,
reporter,
context
)
}
is ConeInapplicableCandidateError -> {
if (originalDestructuringDeclarationType.isNullable) {
reporter.report(
FirErrors.COMPONENT_FUNCTION_ON_NULLABLE.on(
originalDestructuringDeclarationOrInitializerSource,
(diagnostic.candidate.symbol.fir as FirSimpleFunction).name
),
context
)
}
}
// TODO: COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH
}
}
@@ -128,6 +112,79 @@ object FirDestructuringDeclarationChecker : FirPropertyChecker() {
}
}
private fun checkComponentCallReturnType(
source: FirSourceElement,
property: FirProperty,
componentCall: FirComponentCall,
destructuringDeclaration: FirVariable<*>,
reference: FirResolvedNamedReference,
reporter: DiagnosticReporter,
context: CheckerContext
) {
val destructuringType = componentCall.typeRef.coneType
if (destructuringType is ConeKotlinErrorType) {
// There will be other errors on this error type.
return
}
val expectedType = property.returnTypeRef.coneType
if (!AbstractTypeChecker.isSubtypeOf(context.session.typeContext, destructuringType, expectedType)) {
val typeMismatchSource =
// ... = { `(entry, ...)` -> ... } // Report on specific `entry`
if (destructuringDeclaration is FirValueParameter)
property.source
// val (entry, ...) = `destructuring_declaration` // Report on a destructuring declaration
else
source
reporter.reportOn(
typeMismatchSource,
FirErrors.COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH,
(reference.resolvedSymbol.fir as FirSimpleFunction).name,
destructuringType,
expectedType,
context
)
}
}
private fun checkComponentCall(
source: FirSourceElement,
destructuringDeclarationType: ConeKotlinType,
reference: FirErrorNamedReference,
reporter: DiagnosticReporter,
context: CheckerContext
) {
when (val diagnostic = reference.diagnostic) {
is ConeUnresolvedNameError -> {
reporter.reportOn(
source,
FirErrors.COMPONENT_FUNCTION_MISSING,
diagnostic.name,
destructuringDeclarationType,
context
)
}
is ConeAmbiguityError -> {
reporter.reportOn(
source,
FirErrors.COMPONENT_FUNCTION_AMBIGUITY,
diagnostic.name,
diagnostic.candidates,
context
)
}
is ConeInapplicableCandidateError -> {
if (destructuringDeclarationType.isNullable) {
reporter.reportOn(
source,
FirErrors.COMPONENT_FUNCTION_ON_NULLABLE,
(diagnostic.candidate.symbol.fir as FirSimpleFunction).name,
context
)
}
}
}
}
private val FirExpression.explicitReceiverOfQualifiedAccess: FirQualifiedAccessExpression?
get() = (this as? FirQualifiedAccess)?.explicitReceiver?.unwrapped as? FirQualifiedAccessExpression
@@ -82,15 +82,8 @@ class ErrorNodeDiagnosticCollectorComponent(collector: AbstractDiagnosticCollect
) {
// Will be handled by [FirDestructuringDeclarationChecker]
if (source.elementType == KtNodeTypes.DESTRUCTURING_DECLARATION_ENTRY) {
// TODO: if all diagnostics are supported, we don't need the following check, and will bail out based on element type.
if (diagnostic is ConeUnresolvedNameError ||
diagnostic is ConeAmbiguityError ||
diagnostic is ConeInapplicableCandidateError
) {
return
}
return
}
if (source.kind == FirFakeSourceElementKind.ImplicitConstructor) {
return
}
@@ -56,6 +56,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_LITERAL_LHS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_AMBIGUITY
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_MISSING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_ON_NULLABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_OVERLOADS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_UPPER_BOUNDS
@@ -695,6 +696,13 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
"Not nullable value required to call ''{0}()'' function of destructuring declaration initializer",
TO_STRING
)
map.put(
COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH,
"''{0}()'' function returns ''{1}'', but ''{2}'' is expected",
TO_STRING,
RENDER_TYPE,
RENDER_TYPE
)
// Control flow diagnostics
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", VARIABLE_NAME)
@@ -33,5 +33,5 @@ fun <T> test(x: T) {
val s4: Pair<Int?, String> = bar(null, null, ::foo)
val s5: Pair<Int, String> = bar(1, "", ::foo)
val (a1: Int, b1: String) = bar(1, "", ::foo)
val (a1: Int, b1: String) = <!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>bar(1, "", ::foo)<!>
}
@@ -1,9 +0,0 @@
// !WITH_NEW_INFERENCE
class A {
operator fun component1() : Int = 1
operator fun component2() : Int = 2
}
fun a(aa : A) {
val (a: String, b1: String) = aa
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !WITH_NEW_INFERENCE
class A {
operator fun component1() : Int = 1
@@ -1,15 +0,0 @@
// !WITH_NEW_INFERENCE
class A {
operator fun component1() = 1
operator fun component2() = 1.0
}
class C {
operator fun iterator(): Iterator<A> = null!!
}
fun test() {
for ((x: Double, y: Int) in C()) {
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !WITH_NEW_INFERENCE
class A {
operator fun component1() = 1
@@ -22,7 +22,7 @@ fun bar(aList: List<A>) {
b checkType { _<String>() }
}
aList.foo { (a: String, b) ->
aList.foo { (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>a: String<!>, b) ->
a checkType { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
b checkType { _<String>() }
}
@@ -42,7 +42,7 @@ fun bar() {
d checkType { _<Short>() }
}
foo { (a: String, b) ->
foo { (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>a: String<!>, b) ->
a checkType { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
b checkType { _<String>() }
}
@@ -0,0 +1,16 @@
// FIR_IDENTICAL
data class A<T>(val i: T)
fun <T> foo(block: (A<T>) -> Unit) {}
fun <T, R> bar() {
foo<R> { (<!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>i: T<!>) ->
i
}
}
data class C<T>(val x: Int, val y: T)
fun <T, S> foo(c: C<T>) {
val (x: Int, y: S) = <!COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH!>c<!>
}
@@ -0,0 +1,27 @@
package
public fun </*0*/ T, /*1*/ R> bar(): kotlin.Unit
public fun </*0*/ T> foo(/*0*/ block: (A<T>) -> kotlin.Unit): kotlin.Unit
public fun </*0*/ T, /*1*/ S> foo(/*0*/ c: C<T>): kotlin.Unit
public final data class A</*0*/ T> {
public constructor A</*0*/ T>(/*0*/ i: T)
public final val i: T
public final operator /*synthesized*/ fun component1(): T
public final /*synthesized*/ fun copy(/*0*/ i: T = ...): A<T>
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
public final data class C</*0*/ T> {
public constructor C</*0*/ T>(/*0*/ x: kotlin.Int, /*1*/ y: T)
public final val x: kotlin.Int
public final val y: T
public final operator /*synthesized*/ fun component1(): kotlin.Int
public final operator /*synthesized*/ fun component2(): T
public final /*synthesized*/ fun copy(/*0*/ x: kotlin.Int = ..., /*1*/ y: T = ...): C<T>
public open override /*1*/ /*synthesized*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*synthesized*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*synthesized*/ fun toString(): kotlin.String
}
@@ -28907,6 +28907,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/typeParameters/deprecatedSyntax.kt");
}
@Test
@TestMetadata("destructuringDeclarations.kt")
public void testDestructuringDeclarations() throws Exception {
runTest("compiler/testData/diagnostics/tests/typeParameters/destructuringDeclarations.kt");
}
@Test
@TestMetadata("dontIntersectUpperBoundWithExpectedType.kt")
public void testDontIntersectUpperBoundWithExpectedType() throws Exception {
@@ -1444,6 +1444,15 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH) { firDiagnostic ->
ComponentFunctionReturnTypeMismatchImpl(
firDiagnostic.a,
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.b),
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.c),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.UNINITIALIZED_VARIABLE) { firDiagnostic ->
UninitializedVariableImpl(
firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir as FirProperty),
@@ -1020,6 +1020,13 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val componentFunctionName: Name
}
abstract class ComponentFunctionReturnTypeMismatch : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = ComponentFunctionReturnTypeMismatch::class
abstract val componentFunctionName: Name
abstract val destructingType: KtType
abstract val expectedType: KtType
}
abstract class UninitializedVariable : KtFirDiagnostic<KtSimpleNameExpression>() {
override val diagnosticClass get() = UninitializedVariable::class
abstract val variable: KtVariableSymbol
@@ -1651,6 +1651,16 @@ internal class ComponentFunctionOnNullableImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class ComponentFunctionReturnTypeMismatchImpl(
override val componentFunctionName: Name,
override val destructingType: KtType,
override val expectedType: KtType,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.ComponentFunctionReturnTypeMismatch(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class UninitializedVariableImpl(
override val variable: KtVariableSymbol,
firDiagnostic: FirPsiDiagnostic<*>,