diff --git a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/WhenToIfIntention.kt b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/WhenToIfIntention.kt index 6b58a9e11079..e7180080fab5 100644 --- a/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/WhenToIfIntention.kt +++ b/plugins/kotlin/code-insight/intentions-k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/WhenToIfIntention.kt @@ -168,8 +168,7 @@ internal class WhenToIfIntention : val lastEntry = entries.lastOrNull() ?: return false return !(entries.any { it != lastEntry && it.isElse }) && !(entries.size == 1 && lastEntry.isElse) && // 'when' with only 'else' branch is not supported - element.subjectExpression !is KtProperty && - entries.none { it.guard != null } // Not implemented: KTIJ-31750 + element.subjectExpression !is KtProperty } /** @@ -210,7 +209,7 @@ internal class WhenToIfIntention : if (entry.isElse || (isTrueOrFalseCondition && i == 1)) { appendExpression(branch) } else { - val condition = psiFactory.combineWhenConditions(entry.conditions, subject, isNullableSubject) + val condition = psiFactory.combineWhenConditions(entry, subject, isNullableSubject) appendFixedText("if (") appendExpression(condition) appendFixedText(")") diff --git a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java index 15a4b5c8759a..d45e1089a562 100644 --- a/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java +++ b/plugins/kotlin/code-insight/intentions-k2/tests/test/org/jetbrains/kotlin/idea/k2/intentions/tests/K2IntentionTestGenerated.java @@ -5949,6 +5949,31 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithEqualityTests.kt"); } + @TestMetadata("whenWithGuardIncorrectTwoConditions.kt") + public void testWhenWithGuardIncorrectTwoConditions() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt"); + } + + @TestMetadata("whenWithGuardNoParentheses.kt") + public void testWhenWithGuardNoParentheses() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt"); + } + + @TestMetadata("whenWithGuardOrOrNoParentheses.kt") + public void testWhenWithGuardOrOrNoParentheses() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt"); + } + + @TestMetadata("whenWithGuardOrOrParentheses.kt") + public void testWhenWithGuardOrOrParentheses() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt"); + } + + @TestMetadata("whenWithGuardParentheses.kt") + public void testWhenWithGuardParentheses() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt"); + } + @TestMetadata("whenWithGuards.kt") public void testWhenWithGuards() throws Exception { runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt"); @@ -5969,6 +5994,11 @@ public abstract class K2IntentionTestGenerated extends AbstractK2IntentionTest { runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleConditionTypes.kt"); } + @TestMetadata("whenWithMultipleGuards.kt") + public void testWhenWithMultipleGuards() throws Exception { + runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt"); + } + @TestMetadata("whenWithNegativePatterns.kt") public void testWhenWithNegativePatterns() throws Exception { runTest("../../../idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithNegativePatterns.kt"); diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/branchedTransformations/branchedTransformationUtils.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/branchedTransformations/branchedTransformationUtils.kt index 4e54c401120a..cb4ec229ba40 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/branchedTransformations/branchedTransformationUtils.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/intentions/branchedTransformations/branchedTransformationUtils.kt @@ -73,15 +73,45 @@ fun KtWhenCondition.generateNewConditionWithSubject(subject: KtExpression?, isNu * * // After: * n == 0 || n == 3 .. + * + * If a guard condition is present, it will be combined with the last condition with the '&&' operation. + * This will be done regardless of the number of entry conditions. + * Even though it's not correct to use `when` guards with multiple conditions in one branch, transforming it in user code is sensible. + * It preserves the intended meaning and makes the code correct. + * + * For example: + * ``` + * when (foo) { + * is String, + * is Int if foo > 0 -> .. + * } + * ``` + * transforms nicely into + * ``` + * if (foo is String || foo is Int && foo > 0) .. + * ``` */ -fun KtPsiFactory.combineWhenConditions(conditions: Array, subject: KtExpression?, isNullableSubject: Boolean) = - when (conditions.size) { +fun KtPsiFactory.combineWhenConditions(entry: KtWhenEntry, subject: KtExpression?, isNullableSubject: Boolean): KtExpression? { + val conditions = entry.conditions + val combinedConditionsWithSubject = when (conditions.size) { 0 -> null 1 -> conditions[0].generateNewConditionWithSubject(subject, isNullableSubject) else -> buildExpression { appendExpressions(conditions.map { it.generateNewConditionWithSubject(subject, isNullableSubject) }, separator = "||") } } + return handleWhenGuard(combinedConditionsWithSubject, entry) +} + +private fun KtPsiFactory.handleWhenGuard(ktExpression: KtExpression?, entry: KtWhenEntry): KtExpression? { + val guardExpression = entry.guard?.getExpression() + if (ktExpression == null) return guardExpression + if (guardExpression == null) return ktExpression + + val needParentheses = guardExpression is KtBinaryExpression && guardExpression.operationToken == KtTokens.OROR + val preparedGuard = if (needParentheses) createExpression("(${guardExpression.text})") else guardExpression + return createExpression("${ktExpression.text} && ${preparedGuard.text}") +} /** * Returns a new [KtWhenExpression] with introduced subject in brackets. diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java index a1c0c3113533..f7e6bea82768 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/intentions/K1IntentionTestGenerated.java @@ -3191,6 +3191,31 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest { runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithEqualityTests.kt"); } + @TestMetadata("whenWithGuardIncorrectTwoConditions.kt") + public void testWhenWithGuardIncorrectTwoConditions() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt"); + } + + @TestMetadata("whenWithGuardNoParentheses.kt") + public void testWhenWithGuardNoParentheses() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt"); + } + + @TestMetadata("whenWithGuardOrOrNoParentheses.kt") + public void testWhenWithGuardOrOrNoParentheses() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt"); + } + + @TestMetadata("whenWithGuardOrOrParentheses.kt") + public void testWhenWithGuardOrOrParentheses() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt"); + } + + @TestMetadata("whenWithGuardParentheses.kt") + public void testWhenWithGuardParentheses() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt"); + } + @TestMetadata("whenWithGuards.kt") public void testWhenWithGuards() throws Exception { runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt"); @@ -3211,6 +3236,11 @@ public abstract class K1IntentionTestGenerated extends AbstractK1IntentionTest { runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleConditionTypes.kt"); } + @TestMetadata("whenWithMultipleGuards.kt") + public void testWhenWithMultipleGuards() throws Exception { + runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt"); + } + @TestMetadata("whenWithNegativePatterns.kt") public void testWhenWithNegativePatterns() throws Exception { runTest("testData/intentions/branched/ifWhen/whenToIf/whenWithNegativePatterns.kt"); diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt new file mode 100644 index 000000000000..5a4c61523970 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt @@ -0,0 +1,10 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String, + is Int if s > 5 -> { println("1") } + else -> { println("2") } + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt.after new file mode 100644 index 000000000000..6341bc2a0b66 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardIncorrectTwoConditions.kt.after @@ -0,0 +1,7 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String || s is Int && s > 5) { println("1") } + else { println("2") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt new file mode 100644 index 000000000000..4adf5c3f379d --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt @@ -0,0 +1,10 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String -> println("1") + is Int if s > 5 -> { println("2") } + else -> { println("3") } + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt.after new file mode 100644 index 000000000000..a81ee57ae2b5 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardNoParentheses.kt.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String) println("1") + else if (s is Int && s > 5) { println("2") } + else { println("3") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt new file mode 100644 index 000000000000..195997bd226f --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt @@ -0,0 +1,10 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String -> println("1") + is Int if s > 5 || s < 3 -> { println("2") } + else -> { println("3") } + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt.after new file mode 100644 index 000000000000..62714e2470f1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrNoParentheses.kt.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String) println("1") + else if (s is Int && (s > 5 || s < 3)) { println("2") } + else { println("3") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt new file mode 100644 index 000000000000..7e0e3b93e90a --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt @@ -0,0 +1,10 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String -> println("1") + is Int if (s > 5 || s < 3) -> { println("2") } + else -> { println("3") } + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt.after new file mode 100644 index 000000000000..62714e2470f1 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardOrOrParentheses.kt.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String) println("1") + else if (s is Int && (s > 5 || s < 3)) { println("2") } + else { println("3") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt new file mode 100644 index 000000000000..1c783e0a33cc --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt @@ -0,0 +1,10 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String -> println("1") + is Int if (s > 5) -> { println("2") } + else -> { println("3") } + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt.after new file mode 100644 index 000000000000..4d1b66641d87 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuardParentheses.kt.after @@ -0,0 +1,8 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String) println("1") + else if (s is Int && (s > 5)) { println("2") } + else { println("3") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt index 844466f12b99..f89087bcef69 100644 --- a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt @@ -1,6 +1,5 @@ -// IS_APPLICABLE: false // WITH_STDLIB -// Issue: KTIJ-31745 +// IGNORE_K1 private sealed class MySealed { object A : MySealed() @@ -14,6 +13,3 @@ private fun mySealed(s: MySealed) { else -> { println("3") } } } - -// Not enabled to have the test work for K1: -Xwhen-guards doesn't fix the error because of a low language version -// ERROR: The feature "when guards" is experimental and should be enabled explicitly. This can be done by supplying the compiler argument '-Xwhen-guards', but note that no stability guarantees are provided. diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt.after new file mode 100644 index 000000000000..58b596b65083 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithGuards.kt.after @@ -0,0 +1,13 @@ +// WITH_STDLIB +// IGNORE_K1 + +private sealed class MySealed { + object A : MySealed() + class B(val x: Int) : MySealed() +} + +private fun mySealed(s: MySealed) { + if (s is MySealed.A) println("1") + else if (s is MySealed.B && (s.x > 5)) { println("2") } + else { println("3") } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt new file mode 100644 index 000000000000..03d5e0e7ef0c --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt @@ -0,0 +1,13 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + when (s) { + is String -> println("1") + is Int if s > 5 -> println("2") + else if s.toString() == "foo" -> println("3") + is Int if s < 0 -> println("4") + else if s.toString() == "bar" -> println("5") + else -> println("6") + } +} diff --git a/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt.after b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt.after new file mode 100644 index 000000000000..58eb84172d64 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/intentions/branched/ifWhen/whenToIf/whenWithMultipleGuards.kt.after @@ -0,0 +1,11 @@ +// WITH_STDLIB +// IGNORE_K1 + +private fun test(s: Any) { + if (s is String) println("1") + else if (s is Int && s > 5) println("2") + else if (s.toString() == "foo") println("3") + else if (s is Int && s < 0) println("4") + else if (s.toString() == "bar") println("5") + else println("6") +}