Warn about comma-separated conditions in when without argument.

See KT-5143.
This commit is contained in:
Dmitry Petrov
2015-11-19 18:21:46 +03:00
parent b9e9c47b28
commit da90c21284
18 changed files with 215 additions and 19 deletions
@@ -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
@@ -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");
@@ -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 -1
View File
@@ -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
}
}
@@ -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
}
@@ -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 {
@@ -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 = "||")
}
}
}
}
@@ -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)
}
}
@@ -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 */ }
}
}
@@ -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");