From 5d10b37246eae71de640e6ff1a2d5503393d5e3b Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Fri, 2 Nov 2018 18:22:18 +0100 Subject: [PATCH] [java] enhanced 'switch' statements: basic highlighting (IDEA-196643) --- .../daemon/impl/analysis/HighlightUtil.java | 89 +++++++++++-------- .../impl/analysis/HighlightVisitorImpl.java | 8 +- .../src/messages/JavaErrorMessages.properties | 2 + .../DuplicateSwitchLabels.java | 4 +- .../EnhancedSwitchStatements.java | 66 ++++++++++++++ .../advHighlighting6/UnsupportedFeatures.java | 5 ++ .../daemon/LightJava12HighlightingTest.kt | 17 ++++ 7 files changed, 150 insertions(+), 41 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchStatements.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt 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 7b281cf4aa64..10d0859e1fb3 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 @@ -1843,16 +1843,19 @@ public class HighlightUtil extends HighlightUtilBase { @Nullable - static HighlightInfo checkCaseStatement(@NotNull PsiSwitchLabelStatement statement) { + static HighlightInfo checkCaseStatement(@NotNull PsiSwitchLabelStatementBase statement) { PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); if (switchStatement == null) { String description = JavaErrorMessages.message("case.statement.outside.switch"); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); } - if (switchStatement.getBody() == null) return null; + + PsiCodeBlock body = switchStatement.getBody(); + if (body == null) return null; + PsiExpression switchExpression = switchStatement.getExpression(); PsiType switchType = switchExpression == null ? PsiType.INT : switchExpression.getType(); - // check constant expression + boolean defaultCase = statement.isDefaultCase(); PsiExpression caseValue = statement.getCaseValue(); // Every case constant expression associated with a switch statement must be assignable ($5.2) to the type of the switch Expression. @@ -1861,9 +1864,8 @@ public class HighlightUtil extends HighlightUtilBase { if (highlightInfo != null) return highlightInfo; } Object value = null; - boolean isEnumSwitch = false; - if (!statement.isDefaultCase() && caseValue != null) { + if (!defaultCase && caseValue != null) { if (caseValue instanceof PsiReferenceExpression) { PsiElement element = ((PsiReferenceExpression)caseValue).resolve(); if (element instanceof PsiEnumConstant) { @@ -1885,12 +1887,10 @@ public class HighlightUtil extends HighlightUtilBase { } // check duplicate - PsiStatement[] statements = switchStatement.getBody().getStatements(); - for (PsiStatement st : statements) { - if (st == statement) continue; - if (!(st instanceof PsiSwitchLabelStatement)) continue; - PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)st; - if (labelStatement.isDefaultCase() != statement.isDefaultCase()) continue; + for (PsiStatement st : body.getStatements()) { + if (st == statement || !(st instanceof PsiSwitchLabelStatementBase)) continue; + PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; + if (labelStatement.isDefaultCase() != defaultCase) continue; PsiExpression caseExpr = labelStatement.getCaseValue(); if (isEnumSwitch && caseExpr instanceof PsiReferenceExpression) { PsiElement element = ((PsiReferenceExpression)caseExpr).resolve(); @@ -1901,30 +1901,11 @@ public class HighlightUtil extends HighlightUtilBase { if (!TypeConversionUtil.areTypesAssignmentCompatible(switchType, caseExpr)) continue; if (!Comparing.equal(ConstantExpressionUtil.computeCastTo(caseExpr, switchType), value)) continue; } - String description = statement.isDefaultCase() - ? JavaErrorMessages.message("duplicate.default.switch.label") - : JavaErrorMessages.message("duplicate.switch.label", value); - PsiElement element = value == null ? statement : caseValue; + String description = defaultCase ? JavaErrorMessages.message("duplicate.default.switch.label") : JavaErrorMessages.message("duplicate.switch.label", value); + PsiElement element = caseValue != null ? caseValue : ObjectUtils.notNull(statement.getFirstChild(), statement); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); } - // must be followed with colon - PsiElement lastChild = statement.getLastChild(); - while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { - lastChild = lastChild.getPrevSibling(); - } - if (!PsiUtil.isJavaToken(lastChild, JavaTokenType.COLON)) { - int start = statement.getTextRange().getEndOffset(); - int end = statement.getTextRange().getEndOffset() + 1; - String description = JavaErrorMessages.message("switch.colon.expected.after.case.label"); - CharSequence chars = statement.getContainingFile().getViewProvider().getContents(); - boolean isAfterEndOfLine = end >= chars.length() || chars.charAt(start) == '\n' || chars.charAt(start) == '\r'; - HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description); - if (isAfterEndOfLine) { - builder.endOfLine(); - } - return builder.create(); - } return null; } @@ -2419,14 +2400,45 @@ public class HighlightUtil extends HighlightUtilBase { @Nullable - static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(@NotNull PsiSwitchStatement statement) { - PsiCodeBlock body = statement.getBody(); + static HighlightInfo checkSwitchBlockStatements(@NotNull PsiSwitchStatement switchStatement, + @NotNull LanguageLevel languageLevel, + @NotNull PsiFile file) { + PsiCodeBlock body = switchStatement.getBody(); if (body != null) { PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); - if (first != null && !(first instanceof PsiSwitchLabelStatement) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { String description = JavaErrorMessages.message("statement.must.be.prepended.with.case.label"); return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); } + + PsiElement element = first, alien = null; + boolean classicLabels = false, enhancedLabels = false, levelChecked = false; + while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { + if (element instanceof PsiSwitchLabeledRuleStatement) { + if (!levelChecked) { + HighlightInfo info = checkFeature(element, Feature.ENHANCED_SWITCH, languageLevel, file); + if (info != null) return info; + levelChecked = true; + } + if (classicLabels) { + alien = element; + break; + } + enhancedLabels = true; + } + else if (element instanceof PsiStatement) { + if (enhancedLabels) { + alien = element; + break; + } + classicLabels = true; + } + element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); + } + if (alien != null) { + String description = JavaErrorMessages.message("different.case.kinds.in.switch"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); + } } return null; @@ -2543,10 +2555,10 @@ public class HighlightUtil extends HighlightUtilBase { PsiElement element2 = results[1].getElement(); if (element1 instanceof PsiMethod && element2 instanceof PsiMethod) { String candidate1 = PsiFormatUtil.formatMethod((PsiMethod)element1, PsiSubstitutor.EMPTY, - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); String candidate2 = PsiFormatUtil.formatMethod((PsiMethod)element2, PsiSubstitutor.EMPTY, - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_TYPE); return JavaErrorMessages.message("incompatible.types.reason.ambiguous.method.reference", candidate1, candidate2); } @@ -2929,7 +2941,8 @@ public class HighlightUtil extends HighlightUtilBase { STATIC_INTERFACE_CALLS(LanguageLevel.JDK_1_8, "feature.static.interface.calls"), 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"); + RAW_LITERALS(LanguageLevel.JDK_12_PREVIEW, "feature.raw.literals"), + ENHANCED_SWITCH(LanguageLevel.JDK_12_PREVIEW, "feature.enhanced.switch"); 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 d42ab2530a8f..114c77bbf3ea 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 @@ -1584,10 +1584,16 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkCaseStatement(statement)); } + @Override + public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) { + super.visitSwitchLabeledRuleStatement(statement); + if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkCaseStatement(statement)); + } + @Override public void visitSwitchStatement(PsiSwitchStatement statement) { super.visitSwitchStatement(statement); - myHolder.add(HighlightUtil.checkStatementPrependedWithCaseInsideSwitch(statement)); + if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchBlockStatements(statement, myLanguageLevel, myFile)); if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSwitchSelectorType(statement, myLanguageLevel)); } diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index 6a9257dff1b4..90e41ed2dfa3 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -274,6 +274,7 @@ unclosed.comment=Unclosed comment exception.already.caught=Exception ''{0}'' has already been caught exception.must.be.disjoint=Types in multi-catch must be disjoint: ''{0}'' is a subclass of ''{1}'' statement.must.be.prepended.with.case.label=Statement must be prepended with case label +different.case.kinds.in.switch=Different case kinds used in the switch void.type.is.not.allowed='void' type is not allowed here single.import.class.conflict=''{0}'' is already defined in a single-type import numeric.overflow.in.expression=Numeric overflow in expression @@ -475,4 +476,5 @@ feature.static.interface.calls=Static interface method calls feature.try.with.resources.refs=Resource references feature.modules=Modules feature.raw.literals=Raw string literals +feature.enhanced.switch=Enhanced 'switch' blocks insufficient.language.level={0} are not supported at language level ''{1}'' diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DuplicateSwitchLabels.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DuplicateSwitchLabels.java index 79109337bec7..541919c77f09 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DuplicateSwitchLabels.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DuplicateSwitchLabels.java @@ -5,9 +5,9 @@ class DuplicateSwitchLabels { void f(final int i) { switch (i) { - default: break; + default: break; case 1: break; - default: break; + default: break; } switch (i) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchStatements.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchStatements.java new file mode 100644 index 000000000000..412fd04498d6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting12/EnhancedSwitchStatements.java @@ -0,0 +1,66 @@ +import java.util.Random; + +class EnhancedSwitchStatements { + static final int FI = 4; + + enum E { E1, E2 } + + void m(String... args) { + String count; + switch (args.length) { + case 0 -> throw new IllegalStateException("no args"); + case 1 -> count = "one"; + default -> { count = "many"; } + } + + switch (new Random().nextInt()) { + case 0 -> throw new IllegalStateException("no args"); + break; + } + switch (new Random().nextInt()) { + case 0 -> throw new IllegalStateException("no args"); + case 1: break; + } + switch (new Random().nextInt()) { + case 0: throw new IllegalStateException("no args"); break; + case 1 -> { System.out.println("one"); } + } + + { case 11 -> System.out.println("hi there"); } + { default -> System.out.println("hi there"); } + + switch (new Random().nextInt()) { + case 42 -> "bingo"; + } + + switch (new Random().nextInt()) { + default -> noop(); + case 1 -> noop(); + default -> noop(); + } + + switch (new Random().nextInt()) { + case FI/2 - 1 -> noop(); + case (1 + 35/16) % 2 -> noop(); + case FI - 8 -> noop(); + } + + final byte b = 127; + switch (new Random().nextInt()) { + case b -> System.out.println("b=" + b + ";"); + case 127 -> System.out.println("sweet spot"); + } + + switch (0) { + case 0 -> noop(); + case "\410" == "!0" ? 1 : 0 -> noop(); + case "" == "" + "" ? 3 : 0 -> noop(); + } + + switch (E.valueOf("E1")) { + case null -> noop(); + } + } + + private static void noop() { } +} \ 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 c245fa2a67e7..db9d451c2e2d 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting6/UnsupportedFeatures.java @@ -24,6 +24,11 @@ class UnsupportedFeatures { switch (list.get(0)) { case "foo": break; } + + switch (list.size()) { + case 0 -> throw new IllegalStateException("empty list"); + default -> System.out.println("it's okay"); + } } 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 new file mode 100644 index 000000000000..60bd20c05b09 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/LightJava12HighlightingTest.kt @@ -0,0 +1,17 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.java.codeInsight.daemon + +import com.intellij.JavaTestUtil +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase + +class LightJava12HighlightingTest : LightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor() = JAVA_12 + override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/daemonCodeAnalyzer/advHighlighting12" + + fun testEnhancedSwitchStatements() = doTest() + + private fun doTest() { + myFixture.configureByFile(getTestName(false) + ".java") + myFixture.checkHighlighting() + } +} \ No newline at end of file