FIR checker: differentiate unsafe infix/operator calls from UNSAFE_CALL

This commit is contained in:
Jinseong Jeon
2021-02-08 11:27:56 -08:00
committed by Dmitriy Novozhilov
parent 1729eff31b
commit 9b1f01ab04
7 changed files with 69 additions and 16 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration import org.jetbrains.kotlin.fir.declarations.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration 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.expressions.WhenMissingCase
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.* 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_IS_NOT_AN_EXPRESSION by error<FirSourceElement, PsiElement>()
val SUPER_NOT_AVAILABLE by error<FirSourceElement, PsiElement>() val SUPER_NOT_AVAILABLE by error<FirSourceElement, PsiElement>()
val ABSTRACT_SUPER_CALL 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") parameter<String>("target")
} }
} }
@@ -73,7 +74,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
val RECURSION_IN_SUPERTYPES by error<FirSourceElement, PsiElement>() val RECURSION_IN_SUPERTYPES by error<FirSourceElement, PsiElement>()
val NOT_A_SUPERTYPE by error<FirSourceElement, PsiElement>() val NOT_A_SUPERTYPE by error<FirSourceElement, PsiElement>()
val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE 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") parameter<FirClass<*>>("otherSuperType")
} }
val SUPERTYPE_INITIALIZED_IN_INTERFACE by error<FirSourceElement, PsiElement>() 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> { val INAPPLICABLE_CANDIDATE by error<FirSourceElement, PsiElement> {
parameter<AbstractFirBasedSymbol<*>>("candidate") parameter<AbstractFirBasedSymbol<*>>("candidate")
} }
val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, PsiElement>() { val INAPPLICABLE_LATEINIT_MODIFIER by error<FirSourceElement, PsiElement> {
parameter<String>("reason") parameter<String>("reason")
} }
} }
@@ -331,7 +332,7 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
parameter<Collection<AbstractFirBasedSymbol<*>>>("candidates") 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") parameter<Name>("componentFunctionName")
} }
@@ -357,11 +358,19 @@ val DIAGNOSTICS_LIST = DiagnosticListBuilder.buildDiagnosticList {
val UNSAFE_CALL by error<FirSourceElement, PsiElement>(PositioningStrategy.DOT_BY_SELECTOR) { val UNSAFE_CALL by error<FirSourceElement, PsiElement>(PositioningStrategy.DOT_BY_SELECTOR) {
parameter<ConeKotlinType>("receiverType") parameter<ConeKotlinType>("receiverType")
} }
val UNSAFE_IMPLICIT_INVOKE_CALL by error<FirSourceElement, PsiElement>() { val UNSAFE_IMPLICIT_INVOKE_CALL by error<FirSourceElement, PsiElement> {
parameter<ConeKotlinType>("receiverType") parameter<ConeKotlinType>("receiverType")
} }
// TODO: val UNSAFE_INFIX_CALL by ... val UNSAFE_INFIX_CALL by error<FirSourceElement, KtExpression> {
// TODO: val UNSAFE_OPERATOR_CALL by ... 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 ... // 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.FirCallableDeclaration
import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirClass
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration 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.expressions.WhenMissingCase
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
@@ -231,6 +232,8 @@ object FirErrors {
// Nullability // Nullability
val UNSAFE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>(SourceElementPositioningStrategies.DOT_BY_SELECTOR) val UNSAFE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>(SourceElementPositioningStrategies.DOT_BY_SELECTOR)
val UNSAFE_IMPLICIT_INVOKE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>() 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 // When expressions
val NO_ELSE_IN_WHEN by error1<FirSourceElement, KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION) val NO_ELSE_IN_WHEN by error1<FirSourceElement, KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap 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.AMBIGUOUS_CALLS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.DECLARATION_NAME 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.FQ_NAMES_IN_TYPES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING 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.UNRESOLVED_REFERENCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNSAFE_CALL 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_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.UNUSED_VARIABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UPPER_BOUND_VIOLATED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.USELESS_VARARG_ON_PARAMETER 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.", "Reference has a nullable type ''{0}'', use explicit \"?.invoke\" to make a function-like call instead.",
RENDER_TYPE 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 // When expressions
map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES) map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES)
@@ -5,12 +5,18 @@
package org.jetbrains.kotlin.fir.analysis.diagnostics package org.jetbrains.kotlin.fir.analysis.diagnostics
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
import org.jetbrains.kotlin.fir.FirSourceElement 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.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.calls.InapplicableWrongReceiver import org.jetbrains.kotlin.fir.resolve.calls.InapplicableWrongReceiver
import org.jetbrains.kotlin.fir.resolve.diagnostics.* import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -58,7 +64,6 @@ private fun mapInapplicableCandidateError(
source: FirSourceElement, source: FirSourceElement,
): FirDiagnostic<*> { ): FirDiagnostic<*> {
// TODO: Need to distinguish SMARTCAST_IMPOSSIBLE // TODO: Need to distinguish SMARTCAST_IMPOSSIBLE
// TODO: handle other UNSAFE_* variants: infix, operator
val rootCause = diagnostic.candidate.diagnostics.find { it.applicability == diagnostic.applicability } val rootCause = diagnostic.candidate.diagnostics.find { it.applicability == diagnostic.applicability }
if (rootCause != null && if (rootCause != null &&
rootCause is InapplicableWrongReceiver && rootCause is InapplicableWrongReceiver &&
@@ -68,6 +73,24 @@ private fun mapInapplicableCandidateError(
if (diagnostic.candidate.callInfo.isImplicitInvoke) { if (diagnostic.candidate.callInfo.isImplicitInvoke) {
return FirErrors.UNSAFE_IMPLICIT_INVOKE_CALL.on(source, rootCause.actualType!!) 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.UNSAFE_CALL.on(source, rootCause.actualType!!)
} }
return FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol) return FirErrors.INAPPLICABLE_CANDIDATE.on(source, diagnostic.candidate.symbol)
@@ -18,15 +18,15 @@ fun test(x : Int?, a : A?) {
a<!UNSAFE_CALL!>.<!>plus(1) a<!UNSAFE_CALL!>.<!>plus(1)
a?.plus(1) a?.plus(1)
a <!UNSAFE_CALL!>plus<!> 1 a <!UNSAFE_INFIX_CALL!>plus<!> 1
a <!UNSAFE_CALL!>+<!> 1 a <!UNSAFE_OPERATOR_CALL!>+<!> 1
<!UNSAFE_CALL!>-<!>a <!UNSAFE_CALL!>-<!>a
a<!UNSAFE_CALL!>.<!>unaryMinus() a<!UNSAFE_CALL!>.<!>unaryMinus()
a?.unaryMinus() a?.unaryMinus()
a<!UNSAFE_CALL!>.<!>div(1) a<!UNSAFE_CALL!>.<!>div(1)
a <!UNSAFE_CALL!>/<!> 1 a <!UNSAFE_OPERATOR_CALL!>/<!> 1
a <!UNSAFE_CALL!>div<!> 1 a <!UNSAFE_INFIX_CALL!>div<!> 1
a?.div(1) a?.div(1)
a.times(1) a.times(1)
@@ -34,8 +34,8 @@ fun test(x : Int?, a : A?) {
a times 1 a times 1
a?.times(1) a?.times(1)
1 <!UNSAFE_CALL!>in<!> a 1 <!UNSAFE_OPERATOR_CALL!>in<!> a
a <!UNSAFE_CALL!>contains<!> 1 a <!UNSAFE_INFIX_CALL!>contains<!> 1
a<!UNSAFE_CALL!>.<!>contains(1) a<!UNSAFE_CALL!>.<!>contains(1)
a?.contains(1) a?.contains(1)
} }
@@ -14,6 +14,6 @@ fun f(): Unit {
val i : Int? = null val i : Int? = null
i <!NONE_APPLICABLE!>+<!> 1 i <!NONE_APPLICABLE!>+<!> 1
set + 1 set + 1
1 <!UNSAFE_CALL!>in<!> set 1 <!UNSAFE_OPERATOR_CALL!>in<!> set
1 in 2 1 in 2
} }
@@ -7,5 +7,5 @@ infix fun Int.bar(i: Int) = i
fun test() { fun test() {
val p = A() val p = A()
// For open value properties, smart casts should not work // 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
} }