FIR checker: differentiate unsafe infix/operator calls from UNSAFE_CALL
This commit is contained in:
committed by
Dmitriy Novozhilov
parent
1729eff31b
commit
9b1f01ab04
+16
-7
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
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.WhenMissingCase
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
@@ -62,7 +63,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
|
||||
val SUPER_IS_NOT_AN_EXPRESSION by error<FirSourceElement, PsiElement>()
|
||||
val SUPER_NOT_AVAILABLE by error<FirSourceElement, PsiElement>()
|
||||
val ABSTRACT_SUPER_CALL by error<FirSourceElement, PsiElement>()
|
||||
val INSTANCE_ACCESS_BEFORE_SUPER_CALL by error<FirSourceElement, PsiElement>() {
|
||||
val INSTANCE_ACCESS_BEFORE_SUPER_CALL by error<FirSourceElement, PsiElement> {
|
||||
parameter<String>("target")
|
||||
}
|
||||
}
|
||||
@@ -73,7 +74,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
|
||||
val RECURSION_IN_SUPERTYPES by error<FirSourceElement, PsiElement>()
|
||||
val NOT_A_SUPERTYPE by error<FirSourceElement, PsiElement>()
|
||||
val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE by error<FirSourceElement, PsiElement>()
|
||||
val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error<FirSourceElement, PsiElement>() {
|
||||
val QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE by error<FirSourceElement, PsiElement> {
|
||||
parameter<FirClass<*>>("otherSuperType")
|
||||
}
|
||||
val SUPERTYPE_INITIALIZED_IN_INTERFACE by error<FirSourceElement, PsiElement>()
|
||||
@@ -155,7 +156,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
|
||||
val INAPPLICABLE_CANDIDATE by error<FirSourceElement, PsiElement> {
|
||||
parameter<AbstractFirBasedSymbol<*>>("candidate")
|
||||
}
|
||||
val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, PsiElement>() {
|
||||
val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, PsiElement> {
|
||||
parameter<String>("reason")
|
||||
}
|
||||
}
|
||||
@@ -331,7 +332,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
|
||||
parameter<Collection<AbstractFirBasedSymbol<*>>>("candidates")
|
||||
}
|
||||
|
||||
val COMPONENT_FUNCTION_ON_NULLABLE by error<FirSourceElement, KtExpression>() {
|
||||
val COMPONENT_FUNCTION_ON_NULLABLE by error<FirSourceElement, KtExpression> {
|
||||
parameter<Name>("componentFunctionName")
|
||||
}
|
||||
|
||||
@@ -357,11 +358,19 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
|
||||
val UNSAFE_CALL by error<FirSourceElement, PsiElement>(PositioningStrategy.DOT_BY_SELECTOR) {
|
||||
parameter<ConeKotlinType>("receiverType")
|
||||
}
|
||||
val UNSAFE_IMPLICIT_INVOKE_CALL by error<FirSourceElement, PsiElement>() {
|
||||
val UNSAFE_IMPLICIT_INVOKE_CALL by error<FirSourceElement, PsiElement> {
|
||||
parameter<ConeKotlinType>("receiverType")
|
||||
}
|
||||
// TODO: val UNSAFE_INFIX_CALL by ...
|
||||
// TODO: val UNSAFE_OPERATOR_CALL by ...
|
||||
val UNSAFE_INFIX_CALL by error<FirSourceElement, KtExpression> {
|
||||
parameter<FirExpression>("lhs")
|
||||
parameter<String>("operator")
|
||||
parameter<FirExpression>("rhs")
|
||||
}
|
||||
val UNSAFE_OPERATOR_CALL by error<FirSourceElement, KtExpression> {
|
||||
parameter<FirExpression>("lhs")
|
||||
parameter<String>("operator")
|
||||
parameter<FirExpression>("rhs")
|
||||
}
|
||||
// TODO: val UNEXPECTED_SAFE_CALL by ...
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.SourceElementPositioningStr
|
||||
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
|
||||
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.WhenMissingCase
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
@@ -231,6 +232,8 @@ object FirErrors {
|
||||
// Nullability
|
||||
val UNSAFE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>(SourceElementPositioningStrategies.DOT_BY_SELECTOR)
|
||||
val UNSAFE_IMPLICIT_INVOKE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>()
|
||||
val UNSAFE_INFIX_CALL by error3<FirSourceElement, KtExpression, FirExpression, String, FirExpression>()
|
||||
val UNSAFE_OPERATOR_CALL by error3<FirSourceElement, KtExpression, FirExpression, String, FirExpression>()
|
||||
|
||||
// When expressions
|
||||
val NO_ELSE_IN_WHEN by error1<FirSourceElement, KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
|
||||
|
||||
+18
@@ -9,6 +9,7 @@ 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.FIR
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FQ_NAMES_IN_TYPES
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING
|
||||
@@ -158,6 +159,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_LABEL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNRESOLVED_REFERENCE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_IMPLICIT_INVOKE_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_INFIX_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_OPERATOR_CALL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNUSED_VARIABLE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER
|
||||
@@ -512,6 +515,21 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
"Reference has a nullable type ''{0}'', use explicit \"?.invoke\" to make a function-like call instead.",
|
||||
RENDER_TYPE
|
||||
)
|
||||
map.put(
|
||||
UNSAFE_INFIX_CALL,
|
||||
"Infix call corresponds to a dot-qualified call ''{0}.{1}({2})'' which is not allowed on a nullable receiver ''{0}''. " +
|
||||
"Use ''?.''-qualified call instead",
|
||||
FIR,
|
||||
TO_STRING,
|
||||
FIR
|
||||
)
|
||||
map.put(
|
||||
UNSAFE_OPERATOR_CALL,
|
||||
"Operator call corresponds to a dot-qualified call ''{0}.{1}({2})'' which is not allowed on a nullable receiver ''{0}''. ",
|
||||
FIR,
|
||||
TO_STRING,
|
||||
FIR
|
||||
)
|
||||
|
||||
// When expressions
|
||||
map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES)
|
||||
|
||||
+24
-1
@@ -5,12 +5,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.diagnostics
|
||||
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.getChild
|
||||
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.isInfix
|
||||
import org.jetbrains.kotlin.fir.declarations.isOperator
|
||||
import org.jetbrains.kotlin.fir.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.InapplicableWrongReceiver
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
@@ -58,7 +64,6 @@ private fun mapInapplicableCandidateError(
|
||||
source: FirSourceElement,
|
||||
): FirDiagnostic<*> {
|
||||
// TODO: Need to distinguish SMARTCAST_IMPOSSIBLE
|
||||
// TODO: handle other UNSAFE_* variants: infix, operator
|
||||
val rootCause = diagnostic.candidate.diagnostics.find { it.applicability == diagnostic.applicability }
|
||||
if (rootCause != null &&
|
||||
rootCause is InapplicableWrongReceiver &&
|
||||
@@ -68,6 +73,24 @@ private fun mapInapplicableCandidateError(
|
||||
if (diagnostic.candidate.callInfo.isImplicitInvoke) {
|
||||
return FirErrors.UNSAFE_IMPLICIT_INVOKE_CALL.on(source, rootCause.actualType!!)
|
||||
}
|
||||
|
||||
val candidateFunction = diagnostic.candidate.symbol.fir as? FirSimpleFunction
|
||||
val candidateFunctionName = candidateFunction?.name
|
||||
val left = diagnostic.candidate.callInfo.explicitReceiver
|
||||
val right = diagnostic.candidate.callInfo.argumentList.arguments.singleOrNull()
|
||||
if (left != null && right != null &&
|
||||
source.elementType == KtNodeTypes.OPERATION_REFERENCE &&
|
||||
(candidateFunction?.isOperator == true || candidateFunction?.isInfix == true)
|
||||
) {
|
||||
val operationToken = source.getChild(KtTokens.IDENTIFIER)
|
||||
if (candidateFunction.isInfix && operationToken?.elementType == KtTokens.IDENTIFIER) {
|
||||
return FirErrors.UNSAFE_INFIX_CALL.on(source, left, candidateFunctionName!!.asString(), right)
|
||||
}
|
||||
if (candidateFunction.isOperator && operationToken == null) {
|
||||
return FirErrors.UNSAFE_OPERATOR_CALL.on(source, left, candidateFunctionName!!.asString(), right)
|
||||
}
|
||||
}
|
||||
|
||||
return FirErrors.UNSAFE_CALL.on(source, rootCause.actualType!!)
|
||||
}
|
||||
return FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol)
|
||||
|
||||
+6
-6
@@ -18,15 +18,15 @@ fun test(x : Int?, a : A?) {
|
||||
|
||||
a<!UNSAFE_CALL!>.<!>plus(1)
|
||||
a?.plus(1)
|
||||
a <!UNSAFE_CALL!>plus<!> 1
|
||||
a <!UNSAFE_CALL!>+<!> 1
|
||||
a <!UNSAFE_INFIX_CALL!>plus<!> 1
|
||||
a <!UNSAFE_OPERATOR_CALL!>+<!> 1
|
||||
<!UNSAFE_CALL!>-<!>a
|
||||
a<!UNSAFE_CALL!>.<!>unaryMinus()
|
||||
a?.unaryMinus()
|
||||
|
||||
a<!UNSAFE_CALL!>.<!>div(1)
|
||||
a <!UNSAFE_CALL!>/<!> 1
|
||||
a <!UNSAFE_CALL!>div<!> 1
|
||||
a <!UNSAFE_OPERATOR_CALL!>/<!> 1
|
||||
a <!UNSAFE_INFIX_CALL!>div<!> 1
|
||||
a?.div(1)
|
||||
|
||||
a.times(1)
|
||||
@@ -34,8 +34,8 @@ fun test(x : Int?, a : A?) {
|
||||
a times 1
|
||||
a?.times(1)
|
||||
|
||||
1 <!UNSAFE_CALL!>in<!> a
|
||||
a <!UNSAFE_CALL!>contains<!> 1
|
||||
1 <!UNSAFE_OPERATOR_CALL!>in<!> a
|
||||
a <!UNSAFE_INFIX_CALL!>contains<!> 1
|
||||
a<!UNSAFE_CALL!>.<!>contains(1)
|
||||
a?.contains(1)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,6 @@ fun f(): Unit {
|
||||
val i : Int? = null
|
||||
i <!NONE_APPLICABLE!>+<!> 1
|
||||
set + 1
|
||||
1 <!UNSAFE_CALL!>in<!> set
|
||||
1 <!UNSAFE_OPERATOR_CALL!>in<!> set
|
||||
1 in 2
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ infix fun Int.bar(i: Int) = i
|
||||
fun test() {
|
||||
val p = A()
|
||||
// For open value properties, smart casts should not work
|
||||
if (p.foo is Int) p.foo <!UNSAFE_CALL!>bar<!> 11
|
||||
if (p.foo is Int) p.foo <!UNSAFE_INFIX_CALL!>bar<!> 11
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user