Warn about comma-separated conditions in when without argument.
See KT-5143.
This commit is contained in:
@@ -16,10 +16,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.cfg;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||
import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind;
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.Modality;
|
||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||
import org.jetbrains.kotlin.lexer.KtToken;
|
||||
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace;
|
||||
@@ -33,8 +40,8 @@ import org.jetbrains.kotlin.types.TypeUtils;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
|
||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
|
||||
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
|
||||
|
||||
public final class WhenChecker {
|
||||
private WhenChecker() {
|
||||
@@ -267,4 +274,19 @@ public final class WhenChecker {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void checkDeprecatedWhenSyntax(@NotNull BindingTrace trace, @NotNull KtWhenExpression expression) {
|
||||
if (expression.getSubjectExpression() != null) return;
|
||||
|
||||
for (KtWhenEntry entry : expression.getEntries()) {
|
||||
if (entry.isElse()) continue;
|
||||
for (PsiElement child = entry.getFirstChild(); child != null; child = child.getNextSibling()) {
|
||||
if (child.getNode().getElementType() == KtTokens.COMMA) {
|
||||
trace.report(Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.on(child));
|
||||
}
|
||||
if (child.getNode().getElementType() == KtTokens.ARROW) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -684,6 +684,7 @@ public interface Errors {
|
||||
DiagnosticFactory0<KtWhenEntry> ELSE_MISPLACED_IN_WHEN = DiagnosticFactory0.create(ERROR, ELSE_ENTRY);
|
||||
DiagnosticFactory0<KtWhenExpression> NO_ELSE_IN_WHEN = DiagnosticFactory0.create(ERROR, WHEN_EXPRESSION);
|
||||
DiagnosticFactory0<KtWhenExpression> NON_EXHAUSTIVE_WHEN = DiagnosticFactory0.create(WARNING, WHEN_EXPRESSION);
|
||||
DiagnosticFactory0<PsiElement> COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT = DiagnosticFactory0.create(WARNING);
|
||||
|
||||
// Type mismatch
|
||||
|
||||
|
||||
+1
@@ -413,6 +413,7 @@ public class DefaultErrorMessages {
|
||||
MAP.put(NULL_FOR_NONNULL_TYPE, "Null can not be a value of a non-null type {0}", RENDER_TYPE);
|
||||
|
||||
MAP.put(ELSE_MISPLACED_IN_WHEN, "'else' entry must be the last one in a when-expression");
|
||||
MAP.put(COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT, "Deprecated syntax. Use '||' instead of commas in when-condition for 'when' without argument");
|
||||
|
||||
MAP.put(NO_ELSE_IN_WHEN, "'when' expression must contain 'else' branch");
|
||||
MAP.put(NON_EXHAUSTIVE_WHEN, "'when' expression contains only some variants and no 'else' branch");
|
||||
|
||||
+2
@@ -73,6 +73,8 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
|
||||
}
|
||||
|
||||
public KotlinTypeInfo visitWhenExpression(KtWhenExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
|
||||
WhenChecker.checkDeprecatedWhenSyntax(contextWithExpectedType.trace, expression);
|
||||
|
||||
components.dataFlowAnalyzer.recordExpectedType(contextWithExpectedType.trace, expression, contextWithExpectedType.expectedType);
|
||||
|
||||
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
public fun test(o: String?): Boolean {
|
||||
return when {
|
||||
// Data flow info should propagate from o == null to o.length
|
||||
o == null, <!DEBUG_INFO_SMARTCAST!>o<!>.length == 0 -> false
|
||||
o == null<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> <!DEBUG_INFO_SMARTCAST!>o<!>.length == 0 -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fun foo(x: Int, y: Int): Int =
|
||||
when {
|
||||
x > 0<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!> y > 0<!COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT!>,<!><!SYNTAX!>,<!> x < 0 -> 1
|
||||
else -> 0
|
||||
}
|
||||
|
||||
fun bar(x: Int): Int =
|
||||
when (x) {
|
||||
0 -> 0
|
||||
else -> 1
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package
|
||||
|
||||
public fun bar(/*0*/ x: kotlin.Int): kotlin.Int
|
||||
public fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Int): kotlin.Int
|
||||
@@ -17148,6 +17148,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("CommaInWhenConditionWithoutArgument.kt")
|
||||
public void testCommaInWhenConditionWithoutArgument() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/CommaInWhenConditionWithoutArgument.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("ElseOnNullableEnum.kt")
|
||||
public void testElseOnNullableEnum() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/when/ElseOnNullableEnum.kt");
|
||||
|
||||
@@ -98,7 +98,8 @@ public class KotlinCleanupInspection(): LocalInspectionTool(), CleanupLocalInspe
|
||||
Errors.CALLABLE_REFERENCE_TO_MEMBER_OR_EXTENSION_WITH_EMPTY_LHS,
|
||||
Errors.DEPRECATED_TYPE_PARAMETER_SYNTAX,
|
||||
Errors.MISPLACED_TYPE_PARAMETER_CONSTRAINTS,
|
||||
Errors.INVOKE_ON_EXTENSION_FUNCTION_WITH_EXPLICIT_DISPATCH_RECEIVER
|
||||
Errors.INVOKE_ON_EXTENSION_FUNCTION_WITH_EXPLICIT_DISPATCH_RECEIVER,
|
||||
Errors.COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT
|
||||
)
|
||||
|
||||
private fun Diagnostic.isObsoleteLabel(): Boolean {
|
||||
|
||||
+12
@@ -145,3 +145,15 @@ public fun KtWhenExpression.introduceSubject(): KtWhenExpression {
|
||||
|
||||
return replaced(whenExpression)
|
||||
}
|
||||
|
||||
fun KtPsiFactory.combineWhenConditions(conditions: Array<KtWhenCondition>, subject: KtExpression?): KtExpression? {
|
||||
when (conditions.size) {
|
||||
0 -> return null
|
||||
1 -> return conditions[0].toExpression(subject)
|
||||
else -> {
|
||||
return buildExpression {
|
||||
appendExpressions(conditions.map { it.toExpression(subject) }, separator = "||")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-15
@@ -20,7 +20,7 @@ import com.intellij.codeInsight.intention.LowPriorityAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.toExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.combineWhenConditions
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
public class WhenToIfIntention : SelfTargetingRangeIntention<KtWhenExpression>(javaClass(), "Replace 'when' with 'if'"), LowPriorityAction {
|
||||
@@ -59,18 +59,4 @@ public class WhenToIfIntention : SelfTargetingRangeIntention<KtWhenExpression>(j
|
||||
|
||||
element.replace(ifExpression)
|
||||
}
|
||||
|
||||
private fun KtPsiFactory.combineWhenConditions(conditions: Array<KtWhenCondition>, subject: KtExpression?): KtExpression? {
|
||||
when (conditions.size()) {
|
||||
0 -> return null
|
||||
|
||||
1 -> return conditions[0].toExpression(subject)
|
||||
|
||||
else -> {
|
||||
return buildExpression {
|
||||
appendExpressions(conditions.map { it.toExpression(subject) }, separator = "||")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.combineWhenConditions
|
||||
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import java.util.*
|
||||
|
||||
|
||||
class CommaInWhenConditionWithoutArgumentFix(element: PsiElement) : KotlinQuickFixAction<PsiElement>(element), CleanupFix {
|
||||
override fun getFamilyName(): String = text
|
||||
override fun getText(): String = "Replace ',' with '||' in when"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val whenExpression = element as? KtWhenExpression ?: return
|
||||
replaceCommasWithOrsInWhenExpression(whenExpression)
|
||||
}
|
||||
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
|
||||
diagnostic.psiElement.parent?.parent?.let { whenExpressionElement ->
|
||||
CommaInWhenConditionWithoutArgumentFix(whenExpressionElement)
|
||||
}
|
||||
|
||||
private class WhenEntryConditionsData(
|
||||
val conditions: Array<KtWhenCondition>,
|
||||
val first: PsiElement,
|
||||
val last: PsiElement,
|
||||
val arrow: PsiElement
|
||||
)
|
||||
|
||||
private fun replaceCommasWithOrsInWhenExpression(whenExpression: KtWhenExpression) {
|
||||
for (whenEntry in whenExpression.entries) {
|
||||
val conditionsData = getConditionsDataOrNull(whenEntry) ?: return
|
||||
val replacement = KtPsiFactory(whenEntry).combineWhenConditions(conditionsData.conditions, null) ?: return
|
||||
whenEntry.deleteChildRange(conditionsData.first, conditionsData.last)
|
||||
whenEntry.addBefore(replacement, conditionsData.arrow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConditionsDataOrNull(whenEntry: KtWhenEntry): WhenEntryConditionsData? {
|
||||
val conditions = ArrayList<KtWhenCondition>()
|
||||
|
||||
var arrow: PsiElement? = null
|
||||
|
||||
var child = whenEntry.firstChild
|
||||
whenEntryChildren@ while (child != null) {
|
||||
when {
|
||||
child is KtWhenConditionWithExpression -> {
|
||||
conditions.add(child)
|
||||
}
|
||||
child.node.elementType == KtTokens.ARROW -> {
|
||||
arrow = child
|
||||
break@whenEntryChildren
|
||||
}
|
||||
}
|
||||
child = child.nextSibling
|
||||
}
|
||||
|
||||
val last = child?.prevSibling
|
||||
|
||||
return if (arrow != null && last != null)
|
||||
WhenEntryConditionsData(conditions.toTypedArray(), whenEntry.firstChild, last, arrow)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -351,5 +351,7 @@ public class QuickFixRegistrar : QuickFixContributor {
|
||||
DEPRECATED_UNARY_PLUS_MINUS.registerFactory(DeprecatedFunctionConventionFix)
|
||||
|
||||
INVOKE_ON_EXTENSION_FUNCTION_WITH_EXPLICIT_DISPATCH_RECEIVER.registerFactory(InvokeOnExtensionFunctionWithExplicitReceiverFix)
|
||||
|
||||
COMMA_IN_WHEN_CONDITION_WITHOUT_ARGUMENT.registerFactory(CommaInWhenConditionWithoutArgumentFix)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace ',' with '||' in when" "true"
|
||||
fun test(i: Int, j: Int) {
|
||||
var b = false
|
||||
when {
|
||||
i > 0<caret>, j > 0 -> { /* some code 1 */ }
|
||||
i < 0, j < 0 -> { /* some code 2 */ }
|
||||
else -> { /* other code */ }
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace ',' with '||' in when" "true"
|
||||
fun test(i: Int, j: Int) {
|
||||
var b = false
|
||||
when {
|
||||
<caret>i > 0 || j > 0 -> { /* some code 1 */ }
|
||||
i < 0 || j < 0 -> { /* some code 2 */ }
|
||||
else -> { /* other code */ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Replace ',' with '||' in when" "true"
|
||||
fun test(i: Int, j: Int) {
|
||||
var b = false
|
||||
when {
|
||||
i > 0<caret>, j > 0 -> { /* some code 1 */ }
|
||||
i < 0, j < 0 -> { /* some code 2 */ }
|
||||
else -> { /* other code */ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// "Replace ',' with '||' in when" "true"
|
||||
fun test(i: Int, j: Int) {
|
||||
var b = false
|
||||
when {
|
||||
<caret>i > 0 || j > 0 -> { /* some code 1 */ }
|
||||
i < 0 || j < 0 -> { /* some code 2 */ }
|
||||
else -> { /* other code */ }
|
||||
}
|
||||
}
|
||||
@@ -4493,6 +4493,21 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/commasInWhenWithoutArgument")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CommasInWhenWithoutArgument extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInCommasInWhenWithoutArgument() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/commasInWhenWithoutArgument"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("commasInConditionWithNoArguments.kt")
|
||||
public void testCommasInConditionWithNoArguments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/commasInWhenWithoutArgument/commasInConditionWithNoArguments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/migration/conflictingExtension")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@@ -7254,6 +7269,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("commasInConditionWithNoArguments.kt")
|
||||
public void testCommasInConditionWithNoArguments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/commasInConditionWithNoArguments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("continueInWhen.kt")
|
||||
public void testContinueInWhen() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/when/continueInWhen.kt");
|
||||
|
||||
Reference in New Issue
Block a user