[java] switch expressions: basic highlighting (IDEA-196643)

This commit is contained in:
Roman Shevchenko
2018-11-13 13:28:15 +01:00
parent 193aa1f2cf
commit 2b84e80868
9 changed files with 123 additions and 21 deletions
@@ -1353,8 +1353,8 @@ public class HighlightUtil extends HighlightUtilBase {
@Nullable
static HighlightInfo checkSwitchSelectorType(@NotNull PsiSwitchStatement statement, @NotNull LanguageLevel level) {
PsiExpression expression = statement.getExpression();
static HighlightInfo checkSwitchSelectorType(@NotNull PsiSwitchBlock switchBlock, @NotNull LanguageLevel level) {
PsiExpression expression = switchBlock.getExpression();
if (expression == null) return null;
PsiType type = expression.getType();
if (type == null) return null;
@@ -1371,7 +1371,9 @@ public class HighlightUtil extends HighlightUtilBase {
String expected = JavaErrorMessages.message(is7 ? "valid.switch.17.selector.types" : "valid.switch.selector.types");
String message = JavaErrorMessages.message("incompatible.types", expected, JavaHighlightUtil.formatType(type));
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertSwitchToIfIntention(statement));
if (switchBlock instanceof PsiSwitchStatement) {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertSwitchToIfIntention((PsiSwitchStatement)switchBlock));
}
if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) {
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddTypeCastFix(PsiType.INT, expression));
QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createWrapWithAdapterFix(PsiType.INT, expression));
@@ -1845,8 +1847,8 @@ public class HighlightUtil extends HighlightUtilBase {
@Nullable
static HighlightInfo checkCaseStatement(@NotNull PsiSwitchLabelStatementBase statement) {
PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement();
if (switchStatement == null) {
PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock();
if (switchBlock == null) {
String description = JavaErrorMessages.message("case.statement.outside.switch");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create();
}
@@ -1855,15 +1857,16 @@ public class HighlightUtil extends HighlightUtilBase {
}
@Nullable
static Collection<HighlightInfo> checkSwitchLabelValues(@NotNull PsiSwitchStatement switchStatement) {
PsiCodeBlock body = switchStatement.getBody();
static Collection<HighlightInfo> checkSwitchLabelValues(@NotNull PsiSwitchBlock switchBlock) {
PsiCodeBlock body = switchBlock.getBody();
if (body == null) return null;
PsiExpression switchExpression = switchStatement.getExpression();
PsiType switchType = switchExpression == null ? PsiType.INT : switchExpression.getType();
PsiExpression selectorExpression = switchBlock.getExpression();
PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType();
MultiMap<Object, PsiElement> values = new MultiMap<>();
Object defaultValue = new Object();
Collection<HighlightInfo> results = new ArrayList<>();
boolean hasDefaultCase = false;
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase)) continue;
@@ -1872,13 +1875,14 @@ public class HighlightUtil extends HighlightUtilBase {
if (defaultCase) {
values.putValue(defaultValue, ObjectUtils.notNull(labelStatement.getFirstChild(), labelStatement));
hasDefaultCase = true;
}
else {
PsiExpressionList expressionList = labelStatement.getCaseValues();
if (expressionList != null) {
for (PsiExpression expr : expressionList.getExpressions()) {
if (switchExpression != null) {
HighlightInfo result = checkAssignability(switchType, expr.getType(), expr, expr);
if (selectorExpression != null) {
HighlightInfo result = checkAssignability(selectorType, expr.getType(), expr, expr);
if (result != null) {
results.add(result);
continue;
@@ -1898,7 +1902,7 @@ public class HighlightUtil extends HighlightUtilBase {
}
}
if (value == null) {
value = ConstantExpressionUtil.computeCastTo(expr, switchType);
value = ConstantExpressionUtil.computeCastTo(expr, selectorType);
}
if (value == null) {
String description = JavaErrorMessages.message("constant.expression.required");
@@ -1922,6 +1926,33 @@ public class HighlightUtil extends HighlightUtilBase {
}
}
if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) {
if (values.isEmpty()) {
String message = JavaErrorMessages.message("switch.expr.empty");
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(switchBlock).descriptionAndTooltip(message).create());
}
else if (!hasDefaultCase) {
if (selectorType instanceof PsiClassType) {
PsiClass type = ((PsiClassType)selectorType).resolve();
if (type != null && type.isEnum()) {
Set<Object> constants = new HashSet<>();
for (PsiField field : type.getFields()) {
if (field instanceof PsiEnumConstant) {
constants.add(field.getName());
}
}
constants.removeAll(values.keySet());
hasDefaultCase = constants.isEmpty();
}
}
if (!hasDefaultCase) {
PsiElement range = ObjectUtils.notNull(selectorExpression, switchBlock);
String message = JavaErrorMessages.message("switch.expr.incomplete");
results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create());
}
}
}
return results;
}
@@ -2416,10 +2447,10 @@ public class HighlightUtil extends HighlightUtilBase {
@Nullable
static HighlightInfo checkSwitchBlockStatements(@NotNull PsiSwitchStatement switchStatement,
static HighlightInfo checkSwitchBlockStatements(@NotNull PsiSwitchBlock switchBlock,
@NotNull LanguageLevel languageLevel,
@NotNull PsiFile file) {
PsiCodeBlock body = switchStatement.getBody();
PsiCodeBlock body = switchBlock.getBody();
if (body != null) {
PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace());
if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) {
@@ -2968,7 +2999,8 @@ public class HighlightUtil extends HighlightUtilBase {
REFS_AS_RESOURCE(LanguageLevel.JDK_1_9, "feature.try.with.resources.refs"),
MODULES(LanguageLevel.JDK_1_9, "feature.modules"),
RAW_LITERALS(LanguageLevel.JDK_12_PREVIEW, "feature.raw.literals"),
ENHANCED_SWITCH(LanguageLevel.JDK_12_PREVIEW, "feature.enhanced.switch");
ENHANCED_SWITCH(LanguageLevel.JDK_12_PREVIEW, "feature.enhanced.switch"),
SWITCH_EXPRESSION(LanguageLevel.JDK_12_PREVIEW, "feature.switch.expressions");
private final LanguageLevel level;
private final String key;
@@ -1591,9 +1591,20 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitSwitchStatement(PsiSwitchStatement statement) {
super.visitSwitchStatement(statement);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchBlockStatements(statement, myLanguageLevel, myFile));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchSelectorType(statement, myLanguageLevel));
if (!myHolder.hasErrorResults()) myHolder.addAll(HighlightUtil.checkSwitchLabelValues(statement));
checkSwitchBlock(statement);
}
@Override
public void visitSwitchExpression(PsiSwitchExpression expression) {
super.visitSwitchExpression(expression);
if (!myHolder.hasErrorResults()) myHolder.add(checkFeature(expression, Feature.SWITCH_EXPRESSION));
checkSwitchBlock(expression);
}
private void checkSwitchBlock(PsiSwitchBlock switchBlock) {
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchBlockStatements(switchBlock, myLanguageLevel, myFile));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchSelectorType(switchBlock, myLanguageLevel));
if (!myHolder.hasErrorResults()) myHolder.addAll(HighlightUtil.checkSwitchLabelValues(switchBlock));
}
@Override
@@ -11,7 +11,7 @@ public class EnclosingLoopOrSwitchMatcherExpression extends EnclosingLoopMatcher
@Override
public Boolean match(PsiElement element) {
if (element instanceof PsiSwitchStatement) return Boolean.TRUE;
if (element instanceof PsiSwitchBlock) return Boolean.TRUE;
return super.match(element);
}
}
@@ -396,7 +396,8 @@ public final class PsiUtil extends PsiUtilCore {
}
if (element instanceof PsiExpressionStatement) {
return isStatement(((PsiExpressionStatement)element).getExpression());
return parent instanceof PsiSwitchLabeledRuleStatement && ((PsiSwitchLabeledRuleStatement)parent).getEnclosingSwitchBlock() instanceof PsiSwitchExpression ||
isStatement(((PsiExpressionStatement)element).getExpression());
}
if (element instanceof PsiDeclarationStatement) {
@@ -3,6 +3,7 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.JavaSourceUtil;
@@ -21,7 +22,9 @@ public class PsiSwitchExpressionImpl extends PsiSwitchBlockImpl implements PsiSw
@Override
public PsiType getType() {
return null; //tbd
//todo[ann] http://cr.openjdk.java.net/~gbierman/switch-expressions.html#jep325-15.29.1
PsiClass objClass = JavaPsiFacade.getInstance(getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, getResolveScope());
return objClass != null ? new PsiImmediateClassType(objClass, PsiSubstitutor.EMPTY) : null;
}
@Override
@@ -262,6 +262,8 @@ constant.expression.required=Constant expression required
duplicate.default.switch.label=Duplicate default label
duplicate.switch.label=Duplicate label ''{0}''
switch.colon.expected.after.case.label=':' expected
switch.expr.empty='switch' expression does not have any case clauses
switch.expr.incomplete='switch' expression does not cover all possible input values
illegal.forward.reference=Illegal forward reference
illegal.self.reference=Illegal self reference
@@ -478,4 +480,5 @@ feature.try.with.resources.refs=Resource references
feature.modules=Modules
feature.raw.literals=Raw string literals
feature.enhanced.switch=Enhanced 'switch' blocks
feature.switch.expressions='switch' expressions
insufficient.language.level={0} are not supported at language level ''{1}''
@@ -0,0 +1,48 @@
import java.util.Random;
class SwitchExpressions {
enum E { E1, E2 }
void m() {
System.out.println(switch (new Random().nextInt()) {
default -> "whatever";
});
System.out.println(<error descr="'switch' expression does not have any case clauses">switch (new Random().nextInt()) { }</error>);
System.out.println(switch (new Random().nextInt()) {
case 0 -> throw new IllegalStateException("no args");
<error descr="Different case kinds used in the switch">case 1:</error> break;
});
System.out.println(
switch (<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">new Object()</error>) {
default -> "whatever";
}
);
System.out.println(switch (E.valueOf("E1")) {
case <error descr="Constant expression required">null</error> -> 0;
case <error descr="An enum switch case label must be the unqualified name of an enumeration constant">E.E1</error> -> 1;
case E2 -> 2;
case <error descr="Incompatible types. Found: 'int', required: 'SwitchExpressions.E'">1</error> -> 1;
});
System.out.println(switch (new Random().nextInt()) {
<error descr="Duplicate default label">default</error> -> -1;
case 1 -> 1;
<error descr="Duplicate default label">default</error> -> 0;
});
System.out.println(switch (<error descr="'switch' expression does not cover all possible input values">new Random().nextInt()</error>) {
case 1 -> 1;
});
System.out.println(switch (<error descr="'switch' expression does not cover all possible input values">E.valueOf("E1")</error>) {
case E1 -> 1;
});
System.out.println(switch (E.valueOf("E1")) {
case E1 -> 1;
case E2 -> 2;
});
}
}
@@ -32,6 +32,9 @@ class UnsupportedFeatures {
switch (list.size()) {
case <error descr="Enhanced 'switch' blocks are not supported at language level '6'">1, 2</error>:
}
System.out.println(<error descr="'switch' expressions are not supported at language level '6'">switch (list.size()) {
default -> "whoa!";
}</error>);
}
void f(<error descr="Receiver parameters are not supported at language level '6'">Object this</error>) { }
@@ -9,6 +9,7 @@ class LightJava12HighlightingTest : LightCodeInsightFixtureTestCase() {
override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/advHighlighting12"
fun testEnhancedSwitchStatements() = doTest()
fun testSwitchExpressions() = doTest()
private fun doTest() {
myFixture.configureByFile(getTestName(false) + ".java")