[FIR] Add UselessCallOnNotNullChecker

This commit is contained in:
vldf
2020-08-31 19:00:43 +03:00
committed by Mikhail Glukhikh
parent 548adc29d0
commit f9a2d01d57
27 changed files with 297 additions and 1 deletions
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val list1: List<Int> = listOf(1)
val list = list1.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>
@@ -0,0 +1,5 @@
FILE: Basic.kt
public final val list1: R|kotlin/collections/List<kotlin/Int>| = R|kotlin/collections/listOf|<R|kotlin/Int|>(Int(1))
public get(): R|kotlin/collections/List<kotlin/Int>|
public final val list: R|kotlin/collections/List<kotlin/Int>| = R|/list1|.R|kotlin/collections/orEmpty|<R|kotlin/Int|>()
public get(): R|kotlin/collections/List<kotlin/Int>|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val list1: List<Int> = listOf(1)
val list = list1.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>
@@ -0,0 +1,5 @@
FILE: NotNullType.kt
public final val list1: R|kotlin/collections/List<kotlin/Int>| = R|kotlin/collections/listOf|<R|kotlin/Int|>(Int(1))
public get(): R|kotlin/collections/List<kotlin/Int>|
public final val list: R|kotlin/collections/List<kotlin/Int>| = R|/list1|.R|kotlin/collections/orEmpty|<R|kotlin/Int|>()
public get(): R|kotlin/collections/List<kotlin/Int>|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val list1: List<Int> = listOf(1)
val list = list1.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>.map { "$it" }
@@ -0,0 +1,8 @@
FILE: NotNullTypeChain.kt
public final val list1: R|kotlin/collections/List<kotlin/Int>| = R|kotlin/collections/listOf|<R|kotlin/Int|>(Int(1))
public get(): R|kotlin/collections/List<kotlin/Int>|
public final val list: R|kotlin/collections/List<kotlin/String>| = R|/list1|.R|kotlin/collections/orEmpty|<R|kotlin/Int|>().R|kotlin/collections/map|<R|kotlin/Int|, R|kotlin/String|>(<L> = map@fun <anonymous>(it: R|kotlin/Int|): R|kotlin/String| <kind=UNKNOWN> {
^ R|<local>/it|.R|kotlin/Any.toString|()
}
)
public get(): R|kotlin/collections/List<kotlin/String>|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s: String? = ""
val blank = s.isNullOrBlank()
@@ -0,0 +1,5 @@
FILE: NullOrBlankSafe.kt
public final val s: R|kotlin/String?| = String()
public get(): R|kotlin/String?|
public final val blank: R|kotlin/Boolean| = R|/s|.R|kotlin/text/isNullOrBlank|()
public get(): R|kotlin/Boolean|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s = ""
val empty = s.<!USELESS_CALL_ON_NOT_NULL!>isNullOrEmpty()<!>
@@ -0,0 +1,5 @@
FILE: NullOrEmpty.kt
public final val s: R|kotlin/String| = String()
public get(): R|kotlin/String|
public final val empty: R|kotlin/Boolean| = R|/s|.R|kotlin/text/isNullOrEmpty|()
public get(): R|kotlin/Boolean|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s: String? = null
val empty = s.isNullOrEmpty()
@@ -0,0 +1,5 @@
FILE: NullOrEmptyFake.kt
public final val s: R|kotlin/String?| = Null(null)
public get(): R|kotlin/String?|
public final val empty: R|kotlin/Boolean| = R|/s|.R|kotlin/text/isNullOrEmpty|()
public get(): R|kotlin/Boolean|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s: String? = ""
val empty = s?.<!USELESS_CALL_ON_NOT_NULL!>isNullOrEmpty()<!>
@@ -0,0 +1,5 @@
FILE: NullOrEmptySafe.kt
public final val s: R|kotlin/String?| = String()
public get(): R|kotlin/String?|
public final val empty: R|kotlin/Boolean?| = R|/s|?.{ $subj$.R|kotlin/text/isNullOrEmpty|() }
public get(): R|kotlin/Boolean?|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val list: List<String>? = null
val empty = list.orEmpty()
@@ -0,0 +1,5 @@
FILE: OrEmptyFake.kt
public final val list: R|kotlin/collections/List<kotlin/String>?| = Null(null)
public get(): R|kotlin/collections/List<kotlin/String>?|
public final val empty: R|kotlin/collections/List<kotlin/String>| = R|/list|.R|kotlin/collections/orEmpty|<R|kotlin/String|>()
public get(): R|kotlin/collections/List<kotlin/String>|
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val list1: List<Int>? = listOf(1)
val list = list1?.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>
@@ -0,0 +1,5 @@
FILE: SafeCall.kt
public final val list1: R|kotlin/collections/List<kotlin/Int>?| = R|kotlin/collections/listOf|<R|kotlin/Int|>(Int(1))
public get(): R|kotlin/collections/List<kotlin/Int>?|
public final val list: R|kotlin/collections/List<kotlin/Int>?| = R|/list1|?.{ $subj$.R|kotlin/collections/orEmpty|<R|kotlin/Int|>() }
public get(): R|kotlin/collections/List<kotlin/Int>?|
@@ -0,0 +1,5 @@
// WITH_RUNTIME
fun test(s: Sequence<Int>) {
val <!UNUSED_VARIABLE!>foo<!> = s.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>
}
@@ -0,0 +1,4 @@
FILE: Sequence.kt
public final fun test(s: R|kotlin/sequences/Sequence<kotlin/Int>|): R|kotlin/Unit| {
lval foo: R|kotlin/sequences/Sequence<kotlin/Int>| = R|<local>/s|.R|kotlin/sequences/orEmpty|<R|kotlin/Int|>()
}
@@ -0,0 +1,4 @@
// WITH_RUNTIME
val s = ""
val s1 = s.<!USELESS_CALL_ON_NOT_NULL!>orEmpty()<!>
@@ -0,0 +1,5 @@
FILE: String.kt
public final val s: R|kotlin/String| = String()
public get(): R|kotlin/String|
public final val s1: R|kotlin/String| = R|/s|.R|kotlin/text/orEmpty|()
public get(): R|kotlin/String|
@@ -349,4 +349,72 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/valueIsNeverRead.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class UselessCallOnNotNullChecker extends AbstractExtendedFirDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInUselessCallOnNotNullChecker() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("Basic.kt")
public void testBasic() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/Basic.kt");
}
@TestMetadata("NotNullType.kt")
public void testNotNullType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NotNullType.kt");
}
@TestMetadata("NotNullTypeChain.kt")
public void testNotNullTypeChain() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NotNullTypeChain.kt");
}
@TestMetadata("NullOrBlankSafe.kt")
public void testNullOrBlankSafe() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrBlankSafe.kt");
}
@TestMetadata("NullOrEmpty.kt")
public void testNullOrEmpty() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmpty.kt");
}
@TestMetadata("NullOrEmptyFake.kt")
public void testNullOrEmptyFake() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmptyFake.kt");
}
@TestMetadata("NullOrEmptySafe.kt")
public void testNullOrEmptySafe() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmptySafe.kt");
}
@TestMetadata("OrEmptyFake.kt")
public void testOrEmptyFake() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/OrEmptyFake.kt");
}
@TestMetadata("SafeCall.kt")
public void testSafeCall() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/SafeCall.kt");
}
@TestMetadata("Sequence.kt")
public void testSequence() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/Sequence.kt");
}
@TestMetadata("String.kt")
public void testString() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/String.kt");
}
}
}
@@ -349,4 +349,72 @@ public class ExtendedFirWithLightTreeDiagnosticsTestGenerated extends AbstractEx
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/unused/valueIsNeverRead.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class UselessCallOnNotNullChecker extends AbstractExtendedFirWithLightTreeDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInUselessCallOnNotNullChecker() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("Basic.kt")
public void testBasic() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/Basic.kt");
}
@TestMetadata("NotNullType.kt")
public void testNotNullType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NotNullType.kt");
}
@TestMetadata("NotNullTypeChain.kt")
public void testNotNullTypeChain() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NotNullTypeChain.kt");
}
@TestMetadata("NullOrBlankSafe.kt")
public void testNullOrBlankSafe() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrBlankSafe.kt");
}
@TestMetadata("NullOrEmpty.kt")
public void testNullOrEmpty() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmpty.kt");
}
@TestMetadata("NullOrEmptyFake.kt")
public void testNullOrEmptyFake() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmptyFake.kt");
}
@TestMetadata("NullOrEmptySafe.kt")
public void testNullOrEmptySafe() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/NullOrEmptySafe.kt");
}
@TestMetadata("OrEmptyFake.kt")
public void testOrEmptyFake() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/OrEmptyFake.kt");
}
@TestMetadata("SafeCall.kt")
public void testSafeCall() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/SafeCall.kt");
}
@TestMetadata("Sequence.kt")
public void testSequence() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/Sequence.kt");
}
@TestMetadata("String.kt")
public void testString() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/UselessCallOnNotNullChecker/String.kt");
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2020 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.extended
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeNullability
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.fir.types.coneType
object UselessCallOnNotNullChecker : FirQualifiedAccessChecker() {
// todo: add 'call may be reduced' in cases like 's?.isNullOrEmpty()' where 's: String? = ""'
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val method = expression.getCallableId() ?: return
val calleeOn = expression.explicitReceiver ?: return
val calleePackageName = calleeOn.getPackage()
val calleeName = method.callableName.asString()
if ("$calleePackageName.$calleeName" !in triggerOn) return
if (calleeOn.getNullability() == ConeNullability.NOT_NULL) {
reporter.report(expression.source, FirErrors.USELESS_CALL_ON_NOT_NULL)
}
}
private fun FirQualifiedAccessExpression.getCallableId() =
((calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol)?.callableId
private fun FirExpression.getPackage() =
(typeRef as? FirResolvedTypeRef)?.coneType?.classId?.packageFqName.toString()
private fun FirExpression.getNullability() = (typeRef as FirResolvedTypeRef).type.nullability
private val triggerOn = setOf(
"kotlin.collections.orEmpty",
"kotlin.sequences.orEmpty",
"kotlin.text.orEmpty",
"kotlin.text.isNullOrEmpty",
"kotlin.text.isNullOrEmpty",
"kotlin.text.isNullOrBlank",
"kotlin.isNullOrBlank",
"kotlin.isNullOrEmpty",
"kotlin.orEmpty"
)
}
@@ -165,4 +165,5 @@ object FirErrors {
val ASSIGNED_VALUE_IS_NEVER_READ by warning0<FirSourceElement, PsiElement>()
val VARIABLE_INITIALIZER_IS_REDUNDANT by warning0<FirSourceElement, PsiElement>()
val VARIABLE_NEVER_READ by warning0<FirSourceElement, PsiElement>()
val USELESS_CALL_ON_NOT_NULL by warning0<FirSourceElement, PsiElement>()
}
@@ -23,6 +23,7 @@ object ExtendedExpressionCheckers : ExpressionCheckers() {
)
override val qualifiedAccessCheckers: List<FirQualifiedAccessChecker> = listOf(
RedundantCallOfConversionMethod
RedundantCallOfConversionMethod,
UselessCallOnNotNullChecker
)
}