Condition of 'if' expression is constant: report enum entries comparison

#KT-28803 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-12-25 12:50:22 +03:00
committed by Mikhail Glukhikh
parent 662c1b6d4a
commit 301b3aad06
26 changed files with 293 additions and 1 deletions
@@ -14,7 +14,11 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.isElseIf
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.unwrapBlockOrParenthesis
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClass
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
@@ -116,12 +120,42 @@ class ConstantConditionIfInspection : AbstractKotlinInspection() {
private fun KtIfExpression.branch(thenBranch: Boolean) = if (thenBranch) then else `else`
private fun KtExpression.constantBooleanValue(context: BindingContext): Boolean? {
val enumEntriesComparison = enumEntriesComparison()
if (enumEntriesComparison != null) {
return enumEntriesComparison
}
val type = getType(context) ?: return null
val constantValue = ConstantExpressionEvaluator.getConstant(this, context)?.toConstantValue(type)
return constantValue?.value as? Boolean
}
private fun KtExpression.enumEntriesComparison(): Boolean? {
if (this !is KtBinaryExpression) return null
val leftEnumEntry = left?.enumEntry() ?: return null
val rightEnumEntry = right?.enumEntry() ?: return null
val leftEnum = leftEnumEntry.containingClass() ?: return null
val rightEnum = rightEnumEntry.containingClass() ?: return null
if (leftEnum != rightEnum) return null
val enumEntries = leftEnum.body?.getChildrenOfType<KtEnumEntry>() ?: return null
val leftIndex = enumEntries.indexOf(leftEnumEntry)
val rightIndex = enumEntries.indexOf(rightEnumEntry)
return when (operationToken) {
KtTokens.EQEQ -> leftIndex == rightIndex
KtTokens.GT -> leftIndex > rightIndex
KtTokens.GTEQ -> leftIndex >= rightIndex
KtTokens.LT -> leftIndex < rightIndex
KtTokens.LTEQ -> leftIndex <= rightIndex
KtTokens.EXCLEQ -> leftIndex != rightIndex
else -> null
}
}
private fun KtExpression.enumEntry(): KtEnumEntry? {
return ((this as? KtDotQualifiedExpression)?.selectorExpression ?: this).mainReference?.resolve() as? KtEnumEntry
}
fun KtExpression.replaceWithBranch(branch: KtExpression, isUsedAsExpression: Boolean, keepBraces: Boolean = false) {
val lastExpression = when {
branch !is KtBlockExpression -> replaced(branch)
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.A == Enum.A) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,5 @@
package test
enum class Enum {
A, B, C
}
@@ -0,0 +1,5 @@
package test
enum class Enum {
A, B, C
}
@@ -0,0 +1,6 @@
// PROBLEM: Condition is always 'true'
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>test.Enum.A == test.Enum.A) 1 else 2)
}
@@ -0,0 +1,6 @@
// PROBLEM: Condition is always 'true'
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,5 @@
package test
enum class Enum {
A, B, C
}
@@ -0,0 +1,5 @@
package test
enum class Enum {
A, B, C
}
@@ -0,0 +1,8 @@
// PROBLEM: Condition is always 'true'
import test.Enum.*
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>A == A) 1 else 2)
}
@@ -0,0 +1,8 @@
// PROBLEM: Condition is always 'true'
import test.Enum.*
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B > Enum.A) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B >= Enum.A) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B >= Enum.B) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B < Enum.C) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B <= Enum.C) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.B <= Enum.B) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'true'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(1)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'false'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(if (<caret>Enum.A != Enum.A) 1 else 2)
}
@@ -0,0 +1,10 @@
// PROBLEM: Condition is always 'false'
enum class Enum {
A, B, C
}
fun foo(x: Int) {}
fun bar() {
foo(2)
}
@@ -1524,6 +1524,56 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
runTest("idea/testData/inspectionsLocal/constantConditionIf/endWithElseIfNoBraces.kt");
}
@TestMetadata("enumEntryEq.kt")
public void testEnumEntryEq() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryEq.kt");
}
@TestMetadata("enumEntryEq2.kt")
public void testEnumEntryEq2() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryEq2.kt");
}
@TestMetadata("enumEntryEq3.kt")
public void testEnumEntryEq3() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryEq3.kt");
}
@TestMetadata("enumEntryGt.kt")
public void testEnumEntryGt() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryGt.kt");
}
@TestMetadata("enumEntryGtEq.kt")
public void testEnumEntryGtEq() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryGtEq.kt");
}
@TestMetadata("enumEntryGtEq2.kt")
public void testEnumEntryGtEq2() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryGtEq2.kt");
}
@TestMetadata("enumEntryLt.kt")
public void testEnumEntryLt() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryLt.kt");
}
@TestMetadata("enumEntryLtEq.kt")
public void testEnumEntryLtEq() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryLtEq.kt");
}
@TestMetadata("enumEntryLtEq2.kt")
public void testEnumEntryLtEq2() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryLtEq2.kt");
}
@TestMetadata("enumEntryNotEq.kt")
public void testEnumEntryNotEq() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/enumEntryNotEq.kt");
}
@TestMetadata("expression.kt")
public void testExpression() throws Exception {
runTest("idea/testData/inspectionsLocal/constantConditionIf/expression.kt");