FIR checker: warn unnecessary non-null assertions

This commit is contained in:
Jinseong Jeon
2021-04-01 11:44:47 -07:00
committed by Mikhail Glukhikh
parent 5229d4e4f4
commit 2ecb6733ed
72 changed files with 268 additions and 246 deletions
@@ -11,7 +11,7 @@ fun takeB(b: B) {}
fun test_1() {
val flag = SomeEnum.A1
val b: B = when (flag!!) {
val b: B = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
SomeEnum.A1 -> B()
SomeEnum.A2 -> B()
}
@@ -21,7 +21,7 @@ fun test_1() {
fun test_2() {
val flag = SomeEnum.A1
val b = when (flag!!) {
val b = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
SomeEnum.A1 -> B()
SomeEnum.A2 -> B()
}
+3 -3
View File
@@ -16,7 +16,7 @@ fun get(f: Boolean) = if (f) {A.A1} else {""}
<!CONFLICTING_OVERLOADS!>fun case2()<!> {
val flag: Any = get(false) //string
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag!!) { // should be NO_ELSE_IN_WHEN
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) { // should be NO_ELSE_IN_WHEN
A.A1 -> B()
A.A2 -> B()
}
@@ -30,7 +30,7 @@ fun get(f: Boolean) = if (f) {A.A1} else {""}
<!CONFLICTING_OVERLOADS!>fun case2()<!> {
val flag: Any = get(true) //A
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag!!) {// should be NO_ELSE_IN_WHEN
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {// should be NO_ELSE_IN_WHEN
A.A1 -> B()
A.A2 -> B()
}
@@ -44,7 +44,7 @@ fun get(f: Boolean) = if (f) {A.A1} else {""}
fun case3() {
val flag = "" //A
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag!!) {// should be NO_ELSE_IN_WHEN
val l1 = <!NO_ELSE_IN_WHEN!>when<!> (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {// should be NO_ELSE_IN_WHEN
A.A1 -> B() //should be INCOMPATIBLE_TYPES
A.A2 -> B() //should be INCOMPATIBLE_TYPES
}
@@ -13,7 +13,7 @@ class C(val b: B)
// TESTCASE NUMBER: 1
fun case1() {
val flag = A.A1
val l0: B = when (flag!!) {
val l0: B = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
A.A1 -> B()
A.A2 -> B()
}
@@ -34,7 +34,7 @@ fun case2() {
fun case3() {
val flag = A.A1
val l1 = when (flag!!) {
val l1 = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
A.A1 -> B()
A.A2 -> B()
}
@@ -68,7 +68,7 @@ fun case5() {
// TESTCASE NUMBER: 6
fun case6() {
val flag: Any = A.A1
val l1 = when (flag!!) {
val l1 = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
A.A1 -> B()
A.A2 -> B()
else -> B()
@@ -91,7 +91,7 @@ fun case7() {
// TESTCASE NUMBER: 8
fun case8() {
val flag: Any = A.A1
val l1: B = when (flag!!) {
val l1: B = when (flag<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>) {
A.A1 -> B()
A.A2 -> B()
else -> B()
@@ -16,5 +16,5 @@ fun foo() {
var c: MutableSet<String>? = null
c = HashSet()
c!!.d = produce()
}
c<!UNNECESSARY_NOT_NULL_ASSERTION!>!!<!>.d = produce()
}
@@ -1,4 +1,4 @@
fun foo() {
val inv = {{}}!!
val inv = {{}}<!NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION!>!!<!>
val bar = {{}}
}
@@ -560,6 +560,11 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
parameter<FirExpression>("rhs")
}
// TODO: val UNEXPECTED_SAFE_CALL by ...
val UNNECESSARY_NOT_NULL_ASSERTION by warning<FirSourceElement, KtExpression>(PositioningStrategy.OPERATOR) {
parameter<ConeKotlinType>("receiverType")
}
val NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION by warning<FirSourceElement, KtExpression>(PositioningStrategy.OPERATOR)
val NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE by warning<FirSourceElement, KtExpression>(PositioningStrategy.OPERATOR)
}
val WHEN_EXPRESSIONS by object : DiagnosticGroup("When expressions") {
@@ -331,6 +331,9 @@ object FirErrors {
val UNSAFE_IMPLICIT_INVOKE_CALL by error1<FirSourceElement, PsiElement, ConeKotlinType>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val UNSAFE_INFIX_CALL by error3<FirSourceElement, KtExpression, FirExpression, String, FirExpression>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val UNSAFE_OPERATOR_CALL by error3<FirSourceElement, KtExpression, FirExpression, String, FirExpression>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val UNNECESSARY_NOT_NULL_ASSERTION by warning1<FirSourceElement, KtExpression, ConeKotlinType>(SourceElementPositioningStrategies.OPERATOR)
val NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION by warning0<FirSourceElement, KtExpression>(SourceElementPositioningStrategies.OPERATOR)
val NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE by warning0<FirSourceElement, KtExpression>(SourceElementPositioningStrategies.OPERATOR)
// When expressions
val NO_ELSE_IN_WHEN by error1<FirSourceElement, KtWhenExpression, List<WhenMissingCase>>(SourceElementPositioningStrategies.WHEN_EXPRESSION)
@@ -0,0 +1,46 @@
/*
* 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.expression
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess
import org.jetbrains.kotlin.fir.expressions.FirCheckNotNullCall
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.*
object FirCheckNotNullCallChecker : FirBasicExpressionChecker() {
override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
if (expression !is FirCheckNotNullCall) return
val argument = expression.argumentList.arguments.singleOrNull() ?: return
if (argument is FirAnonymousFunction && argument.isLambda) {
reporter.reportOn(expression.source, FirErrors.NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION, context)
return
}
if (argument is FirCallableReferenceAccess) {
reporter.reportOn(expression.source, FirErrors.NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE, context)
return
}
// TODO: use of Unit is subject to change.
// See BodyResolveComponents.typeForQualifier in ResolveUtils.kt which returns Unit for no value type.
if (argument is FirResolvedQualifier && argument.typeRef.isUnit) {
// Would be reported as NO_COMPANION_OBJECT
return
}
val type = argument.typeRef.coneType.fullyExpandedType(context.session)
if (!type.canBeNull) {
reporter.reportOn(expression.source, FirErrors.UNNECESSARY_NOT_NULL_ASSERTION, type, context)
}
}
}
@@ -149,6 +149,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOTHING_TO_OVERRI
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_AN_ANNOTATION_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_LOOP_LABEL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_A_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_ELSE_IN_WHEN
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_GET_METHOD
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NO_SET_METHOD
@@ -217,6 +219,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_IS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.TYPE_PARAMETER_ON_LHS_OF_DOT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNINITIALIZED_VARIABLE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_LATEINIT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.UNNECESSARY_NOT_NULL_ASSERTION
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
@@ -745,6 +748,9 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
TO_STRING,
FIR
)
map.put(UNNECESSARY_NOT_NULL_ASSERTION, "Unnecessary non-null assertion (!!) on a non-null receiver of type {0}", RENDER_TYPE)
map.put(NOT_NULL_ASSERTION_ON_LAMBDA_EXPRESSION, "Non-null assertion (!!) is called on a lambda expression")
map.put(NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE, "Non-null assertion (!!) is called on a callable reference expression")
// When expressions
map.put(NO_ELSE_IN_WHEN, "''when'' expression must be exhaustive, add necessary {0}", WHEN_MISSING_CASES)
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
object CommonExpressionCheckers : ExpressionCheckers() {
override val basicExpressionCheckers: Set<FirBasicExpressionChecker> = setOf(
FirAnonymousFunctionChecker,
FirCheckNotNullCallChecker,
FirGetClassCallChecker,
)