diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 4e6e671e32d2..7f2a00bca4a0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -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 checkSwitchLabelValues(@NotNull PsiSwitchStatement switchStatement) { - PsiCodeBlock body = switchStatement.getBody(); + static Collection 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 values = new MultiMap<>(); Object defaultValue = new Object(); Collection 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 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; diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java index eb322ad5be7f..d9a5697c093f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightVisitorImpl.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/psi/util/EnclosingLoopOrSwitchMatcherExpression.java b/java/java-analysis-impl/src/com/intellij/psi/util/EnclosingLoopOrSwitchMatcherExpression.java index ec745369f7db..e6ad03660dc9 100644 --- a/java/java-analysis-impl/src/com/intellij/psi/util/EnclosingLoopOrSwitchMatcherExpression.java +++ b/java/java-analysis-impl/src/com/intellij/psi/util/EnclosingLoopOrSwitchMatcherExpression.java @@ -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); } } \ No newline at end of file diff --git a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java index 18a59b2cb058..0d76d5afb495 100644 --- a/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java +++ b/java/java-psi-api/src/com/intellij/psi/util/PsiUtil.java @@ -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) { diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiSwitchExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiSwitchExpressionImpl.java index 2d6dc5c86500..fa0825026ec6 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiSwitchExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiSwitchExpressionImpl.java @@ -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 diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index 471e3908135b..cc985b3b872a 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -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}'' diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressions.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressions.java new file mode 100644 index 000000000000..c77e80f9436b --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/SwitchExpressions.java @@ -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(switch (new Random().nextInt()) { }); + + System.out.println(switch (new Random().nextInt()) { + case 0 -> throw new IllegalStateException("no args"); + case 1: break; + }); + + System.out.println( + switch (new Object()) { + default -> "whatever"; + } + ); + + System.out.println(switch (E.valueOf("E1")) { + case null -> 0; + case E.E1 -> 1; + case E2 -> 2; + case 1 -> 1; + }); + + System.out.println(switch (new Random().nextInt()) { + default -> -1; + case 1 -> 1; + default -> 0; + }); + + System.out.println(switch (new Random().nextInt()) { + case 1 -> 1; + }); + System.out.println(switch (E.valueOf("E1")) { + case E1 -> 1; + }); + System.out.println(switch (E.valueOf("E1")) { + case E1 -> 1; + case E2 -> 2; + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java index ccea30d5cdc6..8a9106d62f25 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java @@ -32,6 +32,9 @@ class UnsupportedFeatures { switch (list.size()) { case 1, 2: } + System.out.println(switch (list.size()) { + default -> "whoa!"; + }); } void f(Object this) { } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt index 60bd20c05b09..2fce45d478ef 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt @@ -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")