[FIR] Add empty range checker

This commit is contained in:
vldf
2020-07-28 16:21:49 +03:00
committed by Mikhail Glukhikh
parent b10defdbab
commit 8724efbe8a
8 changed files with 214 additions and 1 deletions

View File

@@ -0,0 +1,29 @@
// WITH_RUNTIME
fun foo() {
for (i in 1..2) { }
val a = 3..4
val v = 1
if (v in 5..6) { }
}
fun backward() {
for (i in 2 downTo 1) { }
val a = 4 downTo 3
val v = 1
if (v in -5 downTo -6) { }
}
fun until() {
for (i in 1 until 2) { }
val a = 3 until 4
val v = 1
if (v in -5 until -4) { }
}

View File

@@ -0,0 +1,43 @@
FILE: NoWarning.kt
public final fun foo(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(1).R|kotlin/Int.rangeTo|(Int(2)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntRange| = Int(3).R|kotlin/Int.rangeTo|(Int(4))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(5).R|kotlin/Int.rangeTo|(Int(6)).R|kotlin/ranges/IntRange.contains|(R|<local>/v|) -> {
}
}
}
public final fun backward(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(2).R|kotlin/ranges/downTo|(Int(1)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntProgression| = Int(4).R|kotlin/ranges/downTo|(Int(3))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(5).R|kotlin/Int.unaryMinus|().R|kotlin/ranges/downTo|(Int(6).R|kotlin/Int.unaryMinus|()).R|kotlin/collections/contains|<R|kotlin/Int|>(R|<local>/v|) -> {
}
}
}
public final fun until(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/until|(Int(2)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntRange| = Int(3).R|kotlin/ranges/until|(Int(4))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(5).R|kotlin/Int.unaryMinus|().R|kotlin/ranges/until|(Int(4).R|kotlin/Int.unaryMinus|()).R|kotlin/ranges/IntRange.contains|(R|<local>/v|) -> {
}
}
}

View File

@@ -0,0 +1,28 @@
// WITH_RUNTIME
fun foo() {
for (i in <!EMPTY_RANGE!>2..1<!>) { }
val a = <!EMPTY_RANGE!>10..0<!>
val v = 1
if (v in <!EMPTY_RANGE!>10..1<!>) { }
}
fun backward() {
for (i in <!EMPTY_RANGE!>1 downTo 2<!>) { }
val a = <!EMPTY_RANGE!>-3 downTo 4<!>
val v = 1
if (v in <!EMPTY_RANGE!>0 downTo 6<!>) { }
}
fun until() {
for (i in <!EMPTY_RANGE!>1 until 1<!>) { }
val a = <!EMPTY_RANGE!>4 until 3<!>
val v = 1
if (v in <!EMPTY_RANGE!>-5 until -5<!>) { }
}

View File

@@ -0,0 +1,43 @@
FILE: Warning.kt
public final fun foo(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(2).R|kotlin/Int.rangeTo|(Int(1)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntRange| = Int(10).R|kotlin/Int.rangeTo|(Int(0))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(10).R|kotlin/Int.rangeTo|(Int(1)).R|kotlin/ranges/IntRange.contains|(R|<local>/v|) -> {
}
}
}
public final fun backward(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/downTo|(Int(2)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntProgression| = Int(3).R|kotlin/Int.unaryMinus|().R|kotlin/ranges/downTo|(Int(4))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(0).R|kotlin/ranges/downTo|(Int(6)).R|kotlin/collections/contains|<R|kotlin/Int|>(R|<local>/v|) -> {
}
}
}
public final fun until(): R|kotlin/Unit| {
lval <iterator>: R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/until|(Int(1)).R|kotlin/ranges/IntProgression.iterator|()
while(R|<local>/<iterator>|.R|kotlin/collections/Iterator.hasNext|()) {
lval i: R|kotlin/Int| = R|<local>/<iterator>|.R|kotlin/collections/IntIterator.next|()
}
lval a: R|kotlin/ranges/IntRange| = Int(4).R|kotlin/ranges/until|(Int(3))
lval v: R|kotlin/Int| = Int(1)
when () {
Int(5).R|kotlin/Int.unaryMinus|().R|kotlin/ranges/until|(Int(5).R|kotlin/Int.unaryMinus|()).R|kotlin/ranges/IntRange.contains|(R|<local>/v|) -> {
}
}
}

View File

@@ -165,4 +165,27 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/validSubtraction.kt");
}
}
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class EmptyRangeChecker extends AbstractExtendedFirDiagnosticsTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInEmptyRangeChecker() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@TestMetadata("NoWarning.kt")
public void testNoWarning() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.kt");
}
@TestMetadata("Warning.kt")
public void testWarning() throws Exception {
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.kt");
}
}
}

View File

@@ -7,12 +7,14 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.analysis.checkers.extended.ArrayEqualityCanBeReplacedWithEquals
import org.jetbrains.kotlin.fir.analysis.checkers.extended.CanBeReplacedWithOperatorAssignmentChecker
import org.jetbrains.kotlin.fir.analysis.checkers.extended.EmptyRangeChecker
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantSingleExpressionStringTemplateChecker
object ExtendedExpressionCheckers : ExpressionCheckers() {
override val expressionCheckers: List<FirBasicExpresionChecker> = listOf(
ArrayEqualityCanBeReplacedWithEquals,
RedundantSingleExpressionStringTemplateChecker
RedundantSingleExpressionStringTemplateChecker,
EmptyRangeChecker
)
override val variableAssignmentCheckers: List<FirVariableAssignmentChecker> = listOf(
CanBeReplacedWithOperatorAssignmentChecker

View File

@@ -0,0 +1,44 @@
/*
* 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.FirFakeSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.psi
object EmptyRangeChecker : FirBasicExpresionChecker() {
override fun check(functionCall: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) {
if (functionCall.source is FirFakeSourceElement<*>) return
if (functionCall !is FirFunctionCall) return
val range = functionCall.psi ?: return
val left = range.children.getOrNull(0)?.text?.toLongOrNull() ?: return
val right = range.children.getOrNull(2)?.text?.toLongOrNull() ?: return
val needReport = when (functionCall.calleeReference.name.asString()) {
"rangeTo" -> {
left > right
}
"downTo" -> {
right > left
}
"until" -> {
left >= right
}
else -> false
}
if (needReport) {
reporter.report(functionCall.source, FirErrors.EMPTY_RANGE)
}
}
}

View File

@@ -93,6 +93,7 @@ object FirErrors {
val CAN_BE_VAL by warning0<FirSourceElement, PsiElement>()
val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0<FirSourceElement, PsiElement>()
val ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS by warning0<FirSourceElement, PsiElement>()
val EMPTY_RANGE by warning0<FirSourceElement, PsiElement>()
}