diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt index 09196cdef6a..afca1236b4f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/branchedTransformations/intentions/IfThenToElvisIntention.kt @@ -27,26 +27,38 @@ import org.jetbrains.kotlin.psi.* class IfThenToElvisInspection : IntentionBasedInspection(IfThenToElvisIntention::class) -class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention(KtIfExpression::class.java, "Replace 'if' expression with elvis expression") { +class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention( + KtIfExpression::class.java, + "Replace 'if' expression with elvis expression" +) { + + private fun KtExpression.clausesReplaceableByElvis(firstClause: KtExpression, secondClause: KtExpression) = + firstClause.isNotNullExpression() && secondClause.evaluatesTo(this) && + !(firstClause is KtThrowExpression && firstClause.throwsNullPointerExceptionWithNoArguments()) override fun isApplicableTo(element: KtIfExpression): Boolean { - val condition = element.condition as? KtBinaryExpression ?: return false + val condition = element.condition as? KtOperationExpression ?: return false val thenClause = element.then ?: return false val elseClause = element.`else` ?: return false - val expression = condition.expressionComparedToNull() ?: return false + val expression = when (condition) { + is KtBinaryExpression -> condition.expressionComparedToNull() ?: return false + is KtIsExpression -> condition.leftHandSide + else -> return false + + } if (!expression.isStableVariable()) return false - return when (condition.operationToken) { - KtTokens.EQEQ -> - thenClause.isNotNullExpression() && elseClause.evaluatesTo(expression) && - !(thenClause is KtThrowExpression && thenClause.throwsNullPointerExceptionWithNoArguments()) - - - KtTokens.EXCLEQ -> - elseClause.isNotNullExpression() && thenClause.evaluatesTo(expression) && - !(elseClause is KtThrowExpression && elseClause.throwsNullPointerExceptionWithNoArguments()) - + return when (condition) { + is KtBinaryExpression -> when (condition.operationToken) { + KtTokens.EQEQ -> expression.clausesReplaceableByElvis(thenClause, elseClause) + KtTokens.EXCLEQ -> expression.clausesReplaceableByElvis(elseClause, thenClause) + else -> false + } + is KtIsExpression -> when (condition.isNegated) { + true -> expression.clausesReplaceableByElvis(thenClause, elseClause) + false -> expression.clausesReplaceableByElvis(elseClause, thenClause) + } else -> false } } @@ -57,22 +69,32 @@ class IfThenToElvisIntention : SelfTargetingOffsetIndependentIntention Pair(elseExpression, thenExpression) - KtTokens.EXCLEQ -> Pair(thenExpression, elseExpression) - else -> throw IllegalArgumentException() - } + val (left, right) = when (condition) { + is KtBinaryExpression -> when (condition.operationToken) { + KtTokens.EQEQ -> Pair(elseExpression, thenExpression) + KtTokens.EXCLEQ -> Pair(thenExpression, elseExpression) + else -> throw IllegalArgumentException() + } + is KtIsExpression -> when (condition.isNegated) { + true -> Pair(elseExpression, thenExpression) + false -> Pair(thenExpression, elseExpression) + } + else -> throw IllegalArgumentException() + } - val newExpr = element.replaced(KtPsiFactory(element).createExpressionByPattern("$0 ?: $1", left, right)) - val elvis = KtPsiUtil.deparenthesize(newExpr) as KtBinaryExpression + val factory = KtPsiFactory(element) + val newExpr = factory.createExpressionByPattern("$0 ?: $1", left, right) as KtBinaryExpression + if (condition is KtIsExpression) { + newExpr.left!!.replace(factory.createExpressionByPattern("$0 as? $1", left, condition.typeReference!!)) + } + val elvis = KtPsiUtil.deparenthesize(element.replaced(newExpr)) as KtBinaryExpression if (editor != null) { elvis.inlineLeftSideIfApplicableWithPrompt(editor) diff --git a/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml b/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml index d9a14948349..efbecb7ecd4 100644 --- a/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml +++ b/idea/testData/intentions/branched/ifThenToElvis/inspectionData/expected.xml @@ -103,4 +103,20 @@ If-Then foldable to '?:' Replace 'if' expression with elvis expression + + notIsCheck.kt + 4 + light_idea_test_case + + If-Then foldable to '?:' + Replace 'if' expression with elvis expression + + + isCheck.kt + 4 + light_idea_test_case + + If-Then foldable to '?:' + Replace 'if' expression with elvis expression + diff --git a/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt b/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt new file mode 100644 index 00000000000..e074f48fba0 --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt @@ -0,0 +1,5 @@ +class My(val x: Int) + +fun foo(arg: Any?): My { + return if (arg is My) arg else My(42) +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt.after b/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt.after new file mode 100644 index 00000000000..f9d0aeccbfd --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/isCheck.kt.after @@ -0,0 +1,5 @@ +class My(val x: Int) + +fun foo(arg: Any?): My { + return arg as? My ?: My(42) +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt b/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt new file mode 100644 index 00000000000..ed6e431d44f --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt @@ -0,0 +1,5 @@ +class My(val x: Int) + +fun foo(arg: Any?): My { + return if (arg !is My) My(42) else arg +} \ No newline at end of file diff --git a/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt.after b/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt.after new file mode 100644 index 00000000000..f9d0aeccbfd --- /dev/null +++ b/idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt.after @@ -0,0 +1,5 @@ +class My(val x: Int) + +fun foo(arg: Any?): My { + return arg as? My ?: My(42) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 40b382951ed..166083bad22 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -1296,6 +1296,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("isCheck.kt") + public void testIsCheck() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/isCheck.kt"); + doTest(fileName); + } + @TestMetadata("lhsEqualsNull.kt") public void testLhsEqualsNull() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/lhsEqualsNull.kt"); @@ -1356,6 +1362,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("notIsCheck.kt") + public void testNotIsCheck() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/notIsCheck.kt"); + doTest(fileName); + } + @TestMetadata("nullBranchAlsoNull.kt") public void testNullBranchAlsoNull() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/branched/ifThenToElvis/nullBranchAlsoNull.kt");