FIR checker: add diagnostics for missing/ambiguous component calls

This commit is contained in:
Jinseong Jeon
2021-01-17 23:51:08 -08:00
committed by Mikhail Glukhikh
parent 83b9c29495
commit fa0b933bc8
17 changed files with 184 additions and 54 deletions
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.extended.report
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.declarations.FirVariable
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.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguityError
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
import org.jetbrains.kotlin.fir.symbols.impl.FirVariableSymbol
object FirDestructuringDeclarationChecker : FirPropertyChecker() {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
val source = declaration.source ?: return
// val (...) = `destructuring_declaration`
if (source.elementType == KtNodeTypes.DESTRUCTURING_DECLARATION) {
assert(declaration.name.isSpecial && declaration.name.asString() == "<destruct>") {
"Unexpected name: ${declaration.name.asString()} for destructuring declaration"
}
checkInitializer(source, declaration.initializer, reporter)
return
}
// val (`destructuring_declaration_entry`, ...) = ...
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 =
when (originalDestructuringDeclaration) {
is FirProperty -> {
if (originalDestructuringDeclaration.initializer?.source?.elementType == KtNodeTypes.FOR) {
// for ((entry, ...) = `destructuring_declaration`) { ... }
// It will be wrapped as `next()` call whose explicit receiver is `iterator()` on the actual source.
val iterator = originalDestructuringDeclaration.initializer?.explicitReceiverOfQualifiedAccess
(iterator?.resolvedVariable as? FirProperty)?.initializer?.explicitReceiverOfQualifiedAccess
} else {
// val (entry, ...) = `destructuring_declaration`
originalDestructuringDeclaration.initializer
}
}
is FirValueParameter -> {
// ... = { `(entry, ...)` -> ... } // value parameter itself is a destructuring declaration
originalDestructuringDeclaration
}
else -> null
} ?: return
val originalDestructuringDeclarationOrInitializerSource = originalDestructuringDeclarationOrInitializer.source ?: return
val originalDestructuringDeclarationType =
when (originalDestructuringDeclarationOrInitializer) {
is FirVariable<*> -> originalDestructuringDeclarationOrInitializer.returnTypeRef
is FirExpression -> originalDestructuringDeclarationOrInitializer.typeRef
else -> null
} ?: return
when (val diagnostic = reference.diagnostic) {
is ConeUnresolvedNameError -> {
reporter.report(
FirErrors.COMPONENT_FUNCTION_MISSING.on(
originalDestructuringDeclarationOrInitializerSource,
diagnostic.name,
originalDestructuringDeclarationType
)
)
}
is ConeAmbiguityError -> {
reporter.report(
FirErrors.COMPONENT_FUNCTION_AMBIGUITY.on(
originalDestructuringDeclarationOrInitializerSource,
diagnostic.name,
diagnostic.candidates
)
)
}
// TODO: COMPONENT_FUNCTION_ON_NULLABLE
// TODO: COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH
}
}
private fun checkInitializer(source: FirSourceElement, initializer: FirExpression?, reporter: DiagnosticReporter) {
val needToReport =
when (initializer) {
null -> true
is FirErrorExpression -> (initializer.diagnostic as? ConeSimpleDiagnostic)?.kind == DiagnosticKind.Syntax
else -> false
}
if (needToReport) {
reporter.report(source, FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION)
}
}
private val FirExpression.explicitReceiverOfQualifiedAccess: FirQualifiedAccessExpression?
get() = (this as? FirQualifiedAccess)?.explicitReceiver?.unwrapped as? FirQualifiedAccessExpression
private val FirExpression.unwrapped: FirExpression
get() =
when (this) {
is FirExpressionWithSmartcast -> this.originalExpression
is FirWrappedExpression -> this.expression
else -> this
}
private val FirQualifiedAccessExpression.resolvedVariable: FirVariable<*>?
get() = ((calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirVariableSymbol)?.fir as? FirVariable<*>
}
@@ -1,35 +0,0 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.extended.report
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
import org.jetbrains.kotlin.fir.expressions.FirErrorExpression
object FirDestructuringDeclarationInitializerChecker : FirPropertyChecker() {
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.name.isSpecial || declaration.name.asString() != "<destruct>") return
val source = declaration.source
if (source == null || source.kind is FirFakeSourceElementKind) return
if (source.elementType != KtNodeTypes.DESTRUCTURING_DECLARATION) return
val needToReport =
when (val initializer = declaration.initializer) {
null -> true
is FirErrorExpression -> (initializer.diagnostic as? ConeSimpleDiagnostic)?.kind == DiagnosticKind.Syntax
else -> false
}
if (needToReport) {
reporter.report(source, FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION)
}
}
}
@@ -59,6 +59,14 @@ class ErrorNodeDiagnosticCollectorComponent(collector: AbstractDiagnosticCollect
}
private fun reportFirDiagnostic(diagnostic: ConeDiagnostic, source: FirSourceElement, reporter: DiagnosticReporter) {
// 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) {
return
}
}
val coneDiagnostic = when (diagnostic) {
is ConeUnresolvedReferenceError -> FirErrors.UNRESOLVED_REFERENCE.on(source, diagnostic.name?.asString() ?: "<No name>")
is ConeUnresolvedSymbolError -> FirErrors.UNRESOLVED_REFERENCE.on(source, diagnostic.classId.asString())
@@ -7,9 +7,11 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.AMBIGUOUS_CALLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.PROPERTY_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.TO_STRING
@@ -32,6 +34,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.BREAK_OR_CONTINUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_VAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_IN_SUPERTYPE_FOR_ENUM
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.CONFLICTING_OVERLOADS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONFLICTING_PROJECTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CONSTRUCTOR_IN_INTERFACE
@@ -382,6 +386,18 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
// Destructuring declaration
map.put(INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION, "Initializer required for destructuring declaration")
map.put(
COMPONENT_FUNCTION_MISSING,
"Destructuring declaration initializer of type {1} must have a ''{0}()'' function",
TO_STRING,
RENDER_TYPE
)
map.put(
COMPONENT_FUNCTION_AMBIGUITY,
"Function ''{0}''() is ambiguous for this expression: {1}",
TO_STRING,
AMBIGUOUS_CALLS
)
// Control flow diagnostics
map.put(UNINITIALIZED_VARIABLE, "{0} must be initialized before access", PROPERTY_NAME)
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.types.FirTypeRef
object FirDiagnosticRenderers {
val NULLABLE_STRING = Renderer<String?> { it ?: "null" }
@@ -56,4 +57,15 @@ object FirDiagnosticRenderers {
}
name.asString()
}
val RENDER_TYPE = Renderer { typeRef: FirTypeRef ->
// TODO: need a way to tune granuality, e.g., without parameter names in functional types.
typeRef.render()
}
val AMBIGUOUS_CALLS = Renderer { candidates: Collection<AbstractFirBasedSymbol<*>> ->
candidates.joinToString(separator = "\n", prefix = "\n") { symbol ->
SYMBOL.render(symbol)
}
}
}
@@ -13,12 +13,15 @@ import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.types.KotlinType
object FirErrors {
// Miscellaneous
@@ -174,6 +177,10 @@ object FirErrors {
// Destructuring declaration
val INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION by error0<FirSourceElement, KtDestructuringDeclaration>()
val COMPONENT_FUNCTION_MISSING by error2<FirSourceElement, PsiElement, Name, FirTypeRef>()
val COMPONENT_FUNCTION_AMBIGUITY by error2<FirSourceElement, PsiElement, Name, Collection<AbstractFirBasedSymbol<*>>>()
// TODO: val COMPONENT_FUNCTION_ON_NULLABLE by ...
// TODO: val COMPONENT_FUNCTION_RETURN_TYPE_MISMATCH by ...
// Control flow diagnostics
val UNINITIALIZED_VARIABLE by error1<FirSourceElement, PsiElement, FirPropertySymbol>()
@@ -32,7 +32,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
override val propertyCheckers: Set<FirPropertyChecker> = setOf(
FirInapplicableLateinitChecker,
FirDestructuringDeclarationInitializerChecker,
FirDestructuringDeclarationChecker,
)
override val regularClassCheckers: Set<FirRegularClassChecker> = setOf(
@@ -11,7 +11,7 @@ class MyClass2 {}
<!CONFLICTING_OVERLOADS!>fun MyClass2.component1()<!> = 1.3
fun test(mc1: MyClass, mc2: MyClass2) {
val (<!INAPPLICABLE_CANDIDATE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>) = mc1
val (<!INAPPLICABLE_CANDIDATE!>a<!>, b) = <!COMPONENT_FUNCTION_MISSING!>mc1<!>
val (c) = mc2
//a,b,c are error types
@@ -9,7 +9,7 @@ class C {
}
fun test() {
for ((<!AMBIGUITY!>x<!>, y) in C()) {
for ((x, y) in <!COMPONENT_FUNCTION_AMBIGUITY!>C()<!>) {
}
}
@@ -7,7 +7,7 @@ class C {
}
fun test() {
for ((x, <!UNRESOLVED_REFERENCE!>y<!>) in C()) {
for ((x, y) in <!COMPONENT_FUNCTION_MISSING!>C()<!>) {
}
}
@@ -1,13 +1,13 @@
// !WITH_NEW_INFERENCE
fun useDeclaredVariables() {
<!UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE!>for ((<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>)<!SYNTAX!><!>) {
<!UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE!>for ((a, b)<!SYNTAX!><!>) {
a
b
}<!>
}
fun checkersShouldRun() {
<!UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE!>for ((<!UNRESOLVED_REFERENCE!>@A a<!>, _)<!SYNTAX!><!>) {
<!UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE, UNRESOLVED_REFERENCE!>for ((@A a, _)<!SYNTAX!><!>) {
}<!>
}
@@ -1,11 +1,11 @@
fun useDeclaredVariables() {
val (<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>) = <!UNRESOLVED_REFERENCE!>unresolved<!>
val (a, b) = <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING, UNRESOLVED_REFERENCE!>unresolved<!>
a
b
}
fun checkersShouldRun() {
val (<!UNRESOLVED_REFERENCE!>@A a<!>, _) = <!UNRESOLVED_REFERENCE!>unresolved<!>
val (@A a, _) = <!COMPONENT_FUNCTION_MISSING, UNRESOLVED_REFERENCE!>unresolved<!>
}
annotation class A
@@ -1,11 +1,11 @@
fun useDeclaredVariables() {
<!INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION!>val (<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>)<!>
<!INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION!>val (a, b)<!>
a
b
}
fun checkersShouldRun() {
<!INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION!>val (<!UNRESOLVED_REFERENCE!>@A a<!>, _)<!>
<!INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION!>val (@A a, _)<!>
}
annotation class A
@@ -6,12 +6,12 @@ val receiverAndReturnType = { Int.(<!SYNTAX!><!>)<!SYNTAX!>: Int -> 5<!> }
val receiverAndReturnTypeWithParameter = { Int.(<!UNRESOLVED_REFERENCE!>a<!><!SYNTAX!><!SYNTAX!><!>: Int): Int -> 5<!> }
val returnType = { (<!SYNTAX!><!>): Int -> 5 }
val returnTypeWithParameter = { (<!UNRESOLVED_REFERENCE!>b: Int<!>): Int -> 5 }
val returnTypeWithParameter = { <!COMPONENT_FUNCTION_MISSING!>(b: Int): Int<!> -> 5 }
val receiverWithFunctionType = { ((Int)<!SYNTAX!><!> <!SYNTAX!>-> Int).() -><!> }
val parenthesizedParameters = { (<!UNRESOLVED_REFERENCE!>a: Int<!>) -> }
val parenthesizedParameters2 = { (<!UNRESOLVED_REFERENCE!>b<!>) -> }
val parenthesizedParameters = { <!COMPONENT_FUNCTION_MISSING!>(a: Int)<!> -> }
val parenthesizedParameters2 = { <!COMPONENT_FUNCTION_MISSING!>(b)<!> -> }
val none = { -> }
@@ -9,7 +9,7 @@ fun <X, Y> foo(y: Y, x: (X, Y) -> Unit) {}
fun bar(aInstance: A, bInstance: B) {
foo("") {
(<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>): A, c ->
<!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>(a, b): A<!>, c ->
a <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
b <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
c checkType { _<String>() }
@@ -4,28 +4,28 @@
data class A(val x: Int, val y: String)
fun bar() {
val x = { (<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>): A ->
val x = { <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>(a, b): A<!> ->
a <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
b <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
}
x checkType { _<(A) -> Unit>() }
val y = { (<!UNRESOLVED_REFERENCE!>a: Int<!>, <!UNRESOLVED_REFERENCE!>b<!>): A ->
val y = { <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>(a: Int, b): A<!> ->
a checkType { _<Int>() }
b <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
}
y checkType { _<(A) -> Unit>() }
val y2 = { (<!UNRESOLVED_REFERENCE!>a: Number<!>, <!UNRESOLVED_REFERENCE!>b<!>): A ->
val y2 = { <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>(a: Number, b): A<!> ->
a checkType { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
b <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
}
y2 checkType { _<(A) -> Unit>() }
val z = { (<!UNRESOLVED_REFERENCE!>a: Int<!>, <!UNRESOLVED_REFERENCE!>b: String<!>) ->
val z = { <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>(a: Int, b: String)<!> ->
a checkType { _<Int>() }
b checkType { _<String>() }
}
@@ -46,5 +46,5 @@ fun foo() {
propertyWithType: Int
val
(<!UNRESOLVED_REFERENCE!>a<!>, <!UNRESOLVED_REFERENCE!>b<!>) = 1
(a, b) = <!COMPONENT_FUNCTION_MISSING, COMPONENT_FUNCTION_MISSING!>1<!>
}