diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.kt b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.kt new file mode 100644 index 00000000000..cb47905d662 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.kt @@ -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) { } +} diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.txt b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.txt new file mode 100644 index 00000000000..220e2394091 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.txt @@ -0,0 +1,43 @@ +FILE: NoWarning.kt + public final fun foo(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(1).R|kotlin/Int.rangeTo|(Int(2)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } + public final fun backward(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(2).R|kotlin/ranges/downTo|(Int(1)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } + public final fun until(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/until|(Int(2)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.kt b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.kt new file mode 100644 index 00000000000..e994e46ab15 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.kt @@ -0,0 +1,28 @@ +// WITH_RUNTIME + +fun foo() { + for (i in 2..1) { } + + val a = 10..0 + + val v = 1 + if (v in 10..1) { } +} + +fun backward() { + for (i in 1 downTo 2) { } + + val a = -3 downTo 4 + + val v = 1 + if (v in 0 downTo 6) { } +} + +fun until() { + for (i in 1 until 1) { } + + val a = 4 until 3 + + val v = 1 + if (v in -5 until -5) { } +} diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.txt b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.txt new file mode 100644 index 00000000000..7bf1062174f --- /dev/null +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.txt @@ -0,0 +1,43 @@ +FILE: Warning.kt + public final fun foo(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(2).R|kotlin/Int.rangeTo|(Int(1)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } + public final fun backward(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/downTo|(Int(2)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } + public final fun until(): R|kotlin/Unit| { + lval : R|kotlin/collections/IntIterator| = Int(1).R|kotlin/ranges/until|(Int(1)).R|kotlin/ranges/IntProgression.iterator|() + while(R|/|.R|kotlin/collections/Iterator.hasNext|()) { + lval i: R|kotlin/Int| = R|/|.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|/v|) -> { + } + } + + } diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java index 4d4ad5bdb40..fbe0fe3972e 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirDiagnosticsTestGenerated.java @@ -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"); + } + } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/ExtendedExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/ExtendedExpressionCheckers.kt index 53e1b7833bf..bade493769a 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/ExtendedExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/ExtendedExpressionCheckers.kt @@ -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 = listOf( ArrayEqualityCanBeReplacedWithEquals, - RedundantSingleExpressionStringTemplateChecker + RedundantSingleExpressionStringTemplateChecker, + EmptyRangeChecker ) override val variableAssignmentCheckers: List = listOf( CanBeReplacedWithOperatorAssignmentChecker diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/EmptyRangeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/EmptyRangeChecker.kt new file mode 100644 index 00000000000..64f9da18b69 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/EmptyRangeChecker.kt @@ -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) + + } + } + +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index e7d40476d29..12d504cbcac 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -93,6 +93,7 @@ object FirErrors { val CAN_BE_VAL by warning0() val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0() val ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS by warning0() + val EMPTY_RANGE by warning0() }