FIR checker: report COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH
This commit is contained in:
committed by
Mikhail Glukhikh
parent
e511eec90e
commit
87c50235ed
@@ -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) }
|
||||
}
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+5
-3
@@ -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>()
|
||||
|
||||
+91
-34
@@ -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
|
||||
|
||||
|
||||
+1
-8
@@ -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
|
||||
}
|
||||
|
||||
+8
@@ -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)
|
||||
|
||||
+1
-1
@@ -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)<!>
|
||||
}
|
||||
|
||||
Vendored
-9
@@ -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
|
||||
}
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !WITH_NEW_INFERENCE
|
||||
class A {
|
||||
operator fun component1() : Int = 1
|
||||
|
||||
-15
@@ -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
@@ -1,3 +1,4 @@
|
||||
// FIR_IDENTICAL
|
||||
// !WITH_NEW_INFERENCE
|
||||
class A {
|
||||
operator fun component1() = 1
|
||||
|
||||
+1
-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>() }
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>() }
|
||||
}
|
||||
|
||||
+16
@@ -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<!>
|
||||
}
|
||||
+27
@@ -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
|
||||
}
|
||||
Generated
+6
@@ -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 {
|
||||
|
||||
+9
@@ -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),
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
+10
@@ -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<*>,
|
||||
|
||||
Reference in New Issue
Block a user