[Kotlin] K2: support when guards in when-to-if intention

KTIJ-31750

GitOrigin-RevId: 965d06ddc9e10077bd176913a72fe5415d47c405
This commit is contained in:
Pavel Kirpichenkov
2025-01-13 09:40:41 +00:00
committed by intellij-monorepo-bot
parent 54f6740bd7
commit c6e3aee5d6
18 changed files with 221 additions and 10 deletions
@@ -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(")")
@@ -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");
@@ -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<KtWhenCondition>, 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.
@@ -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");
@@ -0,0 +1,10 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when (s) {
is String,
is Int <caret>if s > 5 -> { println("1") }
else -> { println("2") }
}
}
@@ -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") }
}
@@ -0,0 +1,10 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when (s) {
is String -> println("1")
is Int <caret>if s > 5 -> { println("2") }
else -> { println("3") }
}
}
@@ -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") }
}
@@ -0,0 +1,10 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when (s) {
is String -> println("1")
is Int <caret>if s > 5 || s < 3 -> { println("2") }
else -> { println("3") }
}
}
@@ -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") }
}
@@ -0,0 +1,10 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when (s) {
is String -> println("1")
is Int <caret>if (s > 5 || s < 3) -> { println("2") }
else -> { println("3") }
}
}
@@ -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") }
}
@@ -0,0 +1,10 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when (s) {
is String -> println("1")
is Int <caret>if (s > 5) -> { println("2") }
else -> { println("3") }
}
}
@@ -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") }
}
@@ -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.
@@ -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") }
}
@@ -0,0 +1,13 @@
// WITH_STDLIB
// IGNORE_K1
private fun test(s: Any) {
when<caret> (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")
}
}
@@ -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")
}