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 ea70da146579..78463637833f 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 @@ -63,7 +63,6 @@ import com.intellij.util.ArrayUtilRt; import com.intellij.util.JavaPsiConstructorUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.UIUtil; import com.siyeh.ig.psiutils.ControlFlowUtils; import com.siyeh.ig.psiutils.VariableAccessUtils; @@ -1575,49 +1574,6 @@ public final class HighlightUtil { return null; } - - 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; - - SelectorKind kind = getSwitchSelectorKind(type); - if (kind == SelectorKind.INT) return null; - - LanguageLevel requiredLevel = null; - if (kind == SelectorKind.ENUM) requiredLevel = LanguageLevel.JDK_1_5; - if (kind == SelectorKind.STRING) requiredLevel = LanguageLevel.JDK_1_7; - - if (kind == null || requiredLevel != null && !level.isAtLeast(requiredLevel)) { - boolean is7 = level.isAtLeast(LanguageLevel.JDK_1_7); - String expected = JavaErrorBundle.message(is7 ? "valid.switch.17.selector.types" : "valid.switch.selector.types"); - String message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(type)); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - if (switchBlock instanceof PsiSwitchStatement) { - QuickFixAction.registerQuickFixAction(info, getFixFactory().createConvertSwitchToIfIntention((PsiSwitchStatement)switchBlock)); - } - if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { - QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddTypeCastFix(PsiType.INT, expression)); - QuickFixAction.registerQuickFixAction(info, getFixFactory().createWrapWithAdapterFix(PsiType.INT, expression)); - } - if (requiredLevel != null) { - QuickFixAction.registerQuickFixAction(info, getFixFactory().createIncreaseLanguageLevelFix(requiredLevel)); - } - return info; - } - - PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); - if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { - String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - String message = JavaErrorBundle.message("inaccessible.type", className); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - - return null; - } - @NotNull static Collection checkSwitchExpressionReturnTypeCompatible(@NotNull PsiSwitchExpression switchExpression) { if (!PsiPolyExpressionUtil.isPolyExpression(switchExpression)) return Collections.emptyList(); @@ -2168,108 +2124,6 @@ public final class HighlightUtil { return null; } - @NotNull - static Collection checkSwitchLabelValues(@NotNull PsiSwitchBlock switchBlock) { - PsiCodeBlock body = switchBlock.getBody(); - if (body == null) return Collections.emptyList(); - - 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; - PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; - boolean defaultCase = labelStatement.isDefaultCase(); - - 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 (selectorExpression != null) { - HighlightInfo result = checkAssignability(selectorType, expr.getType(), expr, expr); - if (result != null) { - results.add(result); - continue; - } - } - - Object value = null; - if (expr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)expr).resolve(); - if (element instanceof PsiEnumConstant) { - value = ((PsiEnumConstant)element).getName(); - if (((PsiReferenceExpression)expr).getQualifier() != null) { - String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(message).create()); - continue; - } - } - } - if (value == null) { - value = ConstantExpressionUtil.computeCastTo(expr, selectorType); - } - if (value == null) { - String description = JavaErrorBundle.message("constant.expression.required"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create()); - continue; - } - - values.putValue(value, expr); - } - } - } - } - - for (Map.Entry> entry : values.entrySet()) { - if (entry.getValue().size() > 1) { - Object value = entry.getKey(); - String description = value == defaultValue ? JavaErrorBundle.message("duplicate.default.switch.label") : JavaErrorBundle - .message("duplicate.switch.label", value); - for (PsiElement element : entry.getValue()) { - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create()); - } - } - } - - if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) { - Set missingConstants = new HashSet<>(); - boolean exhaustive = hasDefaultCase; - if (!exhaustive) { - if (!values.isEmpty() && selectorType instanceof PsiClassType) { - PsiClass type = ((PsiClassType)selectorType).resolve(); - if (type != null && type.isEnum()) { - for (PsiField field : type.getFields()) { - if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { - missingConstants.add(field.getName()); - } - } - exhaustive = missingConstants.isEmpty(); - } - } - } - if (!exhaustive) { - PsiElement range = ObjectUtils.notNull(selectorExpression, switchBlock); - String message = JavaErrorBundle.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); - if (!missingConstants.isEmpty()) { - QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddMissingEnumBranchesFix(switchBlock, missingConstants)); - } - QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddSwitchDefaultFix(switchBlock, null)); - results.add(info); - } - } - - return results; - } - @NotNull static Collection checkSwitchExpressionHasResult(@NotNull PsiSwitchExpression switchExpression) { PsiCodeBlock switchBody = switchExpression.getBody(); @@ -2859,75 +2713,6 @@ public final class HighlightUtil { return null; } - - static HighlightInfo checkSwitchBlockStatements(@NotNull PsiSwitchBlock switchBlock, - @NotNull LanguageLevel languageLevel, - @NotNull PsiFile file) { - PsiCodeBlock body = switchBlock.getBody(); - if (body != null) { - PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); - if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); - } - - PsiElement element = first; - PsiStatement alien = null; - boolean classicLabels = false; - boolean enhancedLabels = false; - boolean levelChecked = false; - while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { - if (element instanceof PsiSwitchLabeledRuleStatement) { - if (!levelChecked) { - HighlightInfo info = checkFeature(element, HighlightingFeature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) return info; - levelChecked = true; - } - if (classicLabels) { - alien = (PsiStatement)element; - break; - } - enhancedLabels = true; - } - else if (element instanceof PsiStatement) { - if (enhancedLabels) { - alien = (PsiStatement)element; - break; - } - classicLabels = true; - } - - if (!levelChecked && element instanceof PsiSwitchLabelStatementBase) { - @Nullable PsiCaseLabelElementList values = ((PsiSwitchLabelStatementBase)element).getCaseLabelElementList(); - if (values != null && values.getElementCount() > 1) { - HighlightInfo info = checkFeature(values, HighlightingFeature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) return info; - levelChecked = true; - } - } - - element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); - } - if (alien != null) { - if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { - PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - if (previousRule != null) { - QuickFixAction.registerQuickFixAction(info, getFixFactory().createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); - } - return info; - } - String description = JavaErrorBundle.message("different.case.kinds.in.switch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - } - } - - return null; - } - - static HighlightInfo checkAssertOperatorTypes(@NotNull PsiExpression expression, @Nullable PsiType type) { if (type == null) return null; if (!(expression.getParent() instanceof PsiAssertStatement)) { 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 ded4ab7707af..cd3a81ff024c 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 @@ -1806,9 +1806,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh } private void checkSwitchBlock(@NotNull 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)); + SwitchBlockHighlightingModel model = SwitchBlockHighlightingModel.createInstance(myLanguageLevel, switchBlock, myFile); + if (!myHolder.hasErrorResults()) myHolder.addAll(model.checkSwitchBlockStatements()); + if (!myHolder.hasErrorResults()) myHolder.addAll(model.checkSwitchSelectorType()); + if (!myHolder.hasErrorResults()) myHolder.addAll(model.checkSwitchLabelValues()); } @Override diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java new file mode 100644 index 000000000000..05915cda6742 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/SwitchBlockHighlightingModel.java @@ -0,0 +1,636 @@ +// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.daemon.impl.analysis; + +import com.intellij.codeInsight.daemon.JavaErrorBundle; +import com.intellij.codeInsight.daemon.impl.HighlightInfo; +import com.intellij.codeInsight.daemon.impl.HighlightInfoType; +import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; +import com.intellij.codeInsight.intention.QuickFixFactory; +import com.intellij.openapi.util.Comparing; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PsiClassReferenceType; +import com.intellij.psi.util.*; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.siyeh.ig.psiutils.ControlFlowUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +abstract class SwitchBlockHighlightingModel { + final LanguageLevel myLevel; + final PsiSwitchBlock myBlock; + final PsiFile myFile; + + final Object defaultValue = new Object(); + + SwitchBlockHighlightingModel(@NotNull LanguageLevel languageLevel, + @NotNull PsiSwitchBlock switchBlock, + @NotNull PsiFile psiFile) { + myLevel = languageLevel; + myBlock = switchBlock; + myFile = psiFile; + } + + static SwitchBlockHighlightingModel createInstance(@NotNull LanguageLevel languageLevel, + @NotNull PsiSwitchBlock switchBlock, + @NotNull PsiFile psiFile) { + if (HighlightingFeature.PATTERNS_IN_SWITCH.isSufficient(languageLevel)) { + return new PatternsInSwitchBlockHighlightingModel(languageLevel, switchBlock, psiFile); + } + return new DefaultSwitchBlockHighlightingModel(languageLevel, switchBlock, psiFile); + } + + @NotNull + abstract List checkSwitchBlockStatements(); + + @NotNull + abstract List checkSwitchSelectorType(); + + @NotNull + abstract List checkSwitchLabelValues(); + + QuickFixFactory getFixFactory() { + return QuickFixFactory.getInstance(); + } + + @NotNull + List checkIfAccessibleType(@NotNull PsiExpression selector, @NotNull PsiType selectorType) { + PsiClass member = PsiUtil.resolveClassInClassTypeOnly(selectorType); + if (member != null && !PsiUtil.isAccessible(member.getProject(), member, selector, null)) { + String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + String message = JavaErrorBundle.message("inaccessible.type", className); + return Collections.singletonList( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(selector).descriptionAndTooltip(message).create()); + } + return Collections.emptyList(); + } + + void checkDuplicates(@NotNull MultiMap values, @NotNull List results) { + for (Map.Entry> entry : values.entrySet()) { + if (entry.getValue().size() > 1) { + Object value = entry.getKey(); + String description = value == defaultValue ? JavaErrorBundle.message("duplicate.default.switch.label") : JavaErrorBundle + .message("duplicate.switch.label", value); + for (PsiElement element : entry.getValue()) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create()); + } + } + } + } + + @Nullable + SelectorKind getSwitchSelectorKind(@NotNull PsiType type) { + if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) { + return SelectorKind.INT; + } + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (psiClass != null) { + if (psiClass.isEnum()) { + return SelectorKind.ENUM; + } + if (Comparing.strEqual(psiClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING)) { + return SelectorKind.STRING; + } + } + return null; + } + + enum SelectorKind {INT, ENUM, STRING} +} + +class DefaultSwitchBlockHighlightingModel extends SwitchBlockHighlightingModel { + + DefaultSwitchBlockHighlightingModel(@NotNull LanguageLevel languageLevel, @NotNull PsiSwitchBlock switchBlock, @NotNull PsiFile psiFile) { + super(languageLevel, switchBlock, psiFile); + } + + @NotNull + @Override + List checkSwitchBlockStatements() { + PsiCodeBlock body = myBlock.getBody(); + if (body == null) return Collections.emptyList(); + PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); + if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); + return Collections.singletonList( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create()); + } + PsiElement element = first; + PsiStatement alien = null; + boolean classicLabels = false; + boolean enhancedLabels = false; + boolean levelChecked = false; + while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { + if (element instanceof PsiSwitchLabeledRuleStatement) { + if (!levelChecked) { + HighlightInfo info = HighlightUtil.checkFeature(element, HighlightingFeature.ENHANCED_SWITCH, myLevel, myFile); + if (info != null) return Collections.singletonList(info); + levelChecked = true; + } + if (classicLabels) { + alien = (PsiStatement)element; + break; + } + enhancedLabels = true; + } + else if (element instanceof PsiStatement) { + if (enhancedLabels) { + alien = (PsiStatement)element; + break; + } + classicLabels = true; + } + + if (!levelChecked && element instanceof PsiSwitchLabelStatementBase) { + @Nullable PsiCaseLabelElementList values = ((PsiSwitchLabelStatementBase)element).getCaseLabelElementList(); + if (values != null && values.getElementCount() > 1) { + HighlightInfo info = HighlightUtil.checkFeature(values, HighlightingFeature.ENHANCED_SWITCH, myLevel, myFile); + if (info != null) return Collections.singletonList(info); + levelChecked = true; + } + } + + element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); + } + if (alien == null) return Collections.emptyList(); + if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { + PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); + String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); + if (previousRule != null) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); + } + return Collections.singletonList(info); + } + String description = JavaErrorBundle.message("different.case.kinds.in.switch"); + return Collections.singletonList( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create()); + } + + @NotNull + @Override + List checkSwitchSelectorType() { + PsiExpression selector = myBlock.getExpression(); + if (selector == null) return Collections.emptyList(); + PsiType selectorType = selector.getType(); + if (selectorType == null) return Collections.emptyList(); + + SelectorKind kind = getSwitchSelectorKind(selectorType); + if (kind == SelectorKind.INT) return Collections.emptyList(); + + LanguageLevel requiredLevel = null; + if (kind == SelectorKind.ENUM) requiredLevel = LanguageLevel.JDK_1_5; + if (kind == SelectorKind.STRING) requiredLevel = LanguageLevel.JDK_1_7; + + if (kind == null || requiredLevel != null && !myLevel.isAtLeast(requiredLevel)) { + boolean is7 = myLevel.isAtLeast(LanguageLevel.JDK_1_7); + String expected = JavaErrorBundle.message(is7 ? "valid.switch.17.selector.types" : "valid.switch.selector.types"); + String message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(selectorType)); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(selector).descriptionAndTooltip(message).create(); + if (myBlock instanceof PsiSwitchStatement) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createConvertSwitchToIfIntention((PsiSwitchStatement)myBlock)); + } + if (PsiType.LONG.equals(selectorType) || PsiType.FLOAT.equals(selectorType) || PsiType.DOUBLE.equals(selectorType)) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddTypeCastFix(PsiType.INT, selector)); + QuickFixAction.registerQuickFixAction(info, getFixFactory().createWrapWithAdapterFix(PsiType.INT, selector)); + } + if (requiredLevel != null) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createIncreaseLanguageLevelFix(requiredLevel)); + } + return Collections.singletonList(info); + } + return checkIfAccessibleType(selector, selectorType); + } + + @NotNull + @Override + List checkSwitchLabelValues() { + PsiCodeBlock body = myBlock.getBody(); + if (body == null) return Collections.emptyList(); + + PsiExpression selectorExpression = myBlock.getExpression(); + PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType(); + MultiMap values = new MultiMap<>(); + List results = new ArrayList<>(); + boolean hasDefaultCase = false; + + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase)) continue; + PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; + boolean defaultCase = labelStatement.isDefaultCase(); + if (defaultCase) { + values.putValue(defaultValue, ObjectUtils.notNull(labelStatement.getFirstChild(), labelStatement)); + hasDefaultCase = true; + continue; + } + PsiExpressionList expressionList = labelStatement.getCaseValues(); + if (expressionList == null) { + continue; + } + for (PsiExpression expr : expressionList.getExpressions()) { + if (selectorExpression != null) { + HighlightInfo result = HighlightUtil.checkAssignability(selectorType, expr.getType(), expr, expr); + if (result != null) { + results.add(result); + continue; + } + } + Object value = null; + if (expr instanceof PsiReferenceExpression) { + PsiReferenceExpression refExpr = (PsiReferenceExpression)expr; + PsiElement element = refExpr.resolve(); + if (element instanceof PsiEnumConstant) { + value = ((PsiEnumConstant)element).getName(); + if (refExpr.getQualifier() != null) { + String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(message).create()); + continue; + } + } + } + if (value == null) { + value = ConstantExpressionUtil.computeCastTo(expr, selectorType); + } + if (value == null) { + String description = JavaErrorBundle.message("constant.expression.required"); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create()); + continue; + } + values.putValue(value, expr); + } + } + + checkDuplicates(values, results); + + if (results.isEmpty() && myBlock instanceof PsiSwitchExpression) { + Set missingConstants = new HashSet<>(); + boolean exhaustive = hasDefaultCase; + if (!exhaustive) { + if (!values.isEmpty() && selectorType instanceof PsiClassType) { + PsiClass type = ((PsiClassType)selectorType).resolve(); + if (type != null && type.isEnum()) { + for (PsiField field : type.getFields()) { + if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { + missingConstants.add(field.getName()); + } + } + exhaustive = missingConstants.isEmpty(); + } + } + } + if (!exhaustive) { + PsiElement range = ObjectUtils.notNull(selectorExpression, myBlock); + String message = JavaErrorBundle.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); + if (!missingConstants.isEmpty()) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddMissingEnumBranchesFix(myBlock, missingConstants)); + } + QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddSwitchDefaultFix(myBlock, null)); + results.add(info); + } + } + + return results; + } +} + +class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlightingModel { + private final DefaultSwitchBlockHighlightingModel defaultModel; + + PatternsInSwitchBlockHighlightingModel(@NotNull LanguageLevel languageLevel, + @NotNull PsiSwitchBlock switchBlock, + @NotNull PsiFile psiFile) { + super(languageLevel, switchBlock, psiFile); + defaultModel = new DefaultSwitchBlockHighlightingModel(languageLevel, switchBlock, psiFile); + } + + @NotNull + @Override + List checkSwitchBlockStatements() { + return defaultModel.checkSwitchBlockStatements(); + } + + @NotNull + @Override + List checkSwitchSelectorType() { + PsiExpression selector = myBlock.getExpression(); + if (selector == null) return Collections.emptyList(); + PsiType selectorType = selector.getType(); + if (selectorType == null) return Collections.emptyList(); + return checkIfAccessibleType(selector, selectorType); + } + + @NotNull + @Override + List checkSwitchLabelValues() { + PsiCodeBlock body = myBlock.getBody(); + if (body == null) return Collections.emptyList(); + PsiExpression selectorExpression = myBlock.getExpression(); + if (selectorExpression == null) return Collections.emptyList(); + PsiType selectorType = selectorExpression.getType(); + if (selectorType == null) return Collections.emptyList(); + var elementsToCheckDuplicates = new MultiMap(); + var elementsToCheckFallThroughLegality = new MultiMap(new LinkedHashMap<>()); + List elementsToCheckDominance = new ArrayList<>(); + List results = new ArrayList<>(); + boolean hasDefaultCase = false; + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase)) continue; + PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; + if (labelStatement.isDefaultCase()) { + elementsToCheckDuplicates.putValue(defaultValue, ObjectUtils.notNull(labelStatement.getFirstChild(), labelStatement)); + elementsToCheckFallThroughLegality.put(labelStatement, Collections.emptyList()); + hasDefaultCase = true; + continue; + } + PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList(); + if (labelElementList == null) continue; + for (PsiCaseLabelElement labelElement : labelElementList.getElements()) { + if (labelElement instanceof PsiDefaultCaseLabelElement) { + hasDefaultCase = true; + } + HighlightInfo compatibilityInfo = checkLabelAndSelectorCompatibility(labelElement, selectorType); + if (compatibilityInfo != null) { + results.add(compatibilityInfo); + continue; + } + fillElementsToCheckDuplicates(elementsToCheckDuplicates, labelElement); + fillElementsToCheckFallThroughLegality(elementsToCheckFallThroughLegality, labelStatement, labelElement); + fillElementsToCheckDominance(elementsToCheckDominance, labelElement); + } + } + + checkDuplicates(elementsToCheckDuplicates, results); + if (!results.isEmpty()) return results; + + checkFallThroughFromToPattern(elementsToCheckFallThroughLegality, results); + if (!results.isEmpty()) return results; + + checkDominance(elementsToCheckDominance, selectorType, results); + if (!results.isEmpty()) return results; + + // todo rewrite according the spec + //checkCompleteness(selectorExpression, selectorType, elementsToCheckDuplicates, results, hasDefaultCase); + return results; + } + + @Nullable + private HighlightInfo checkLabelAndSelectorCompatibility(@NotNull PsiCaseLabelElement label, @NotNull PsiType selectorType) { + if (label instanceof PsiDefaultCaseLabelElement) return null; + if (isNullType(label)) { + if (!(selectorType instanceof PsiClassReferenceType)) { + String message = JavaErrorBundle.message("incompatible.switch.17.null.type", "null", selectorType.getPresentableText()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(label) + .descriptionAndTooltip(message).create(); + } + return null; + } + else if (label instanceof PsiPattern) { + PsiType patternType = JavaPsiPatternUtil.getPatternType((PsiPattern)label); + if (patternType != null && !TypeConversionUtil.isAssignable(selectorType, patternType) && + !TypeConversionUtil.isAssignable(patternType, selectorType)) { + return HighlightUtil.createIncompatibleTypeHighlightInfo(selectorType, patternType, label.getTextRange(), 0); + } + return null; + } + else if (label instanceof PsiExpression) { + PsiExpression expr = (PsiExpression)label; + if (label instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)label).resolve(); + if (element instanceof PsiEnumConstant) { + if (((PsiReferenceExpression)label).getQualifier() != null) { + String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(label).descriptionAndTooltip(message).create(); + } + return null; + } + } + if (ConstantExpressionUtil.computeCastTo(expr, selectorType) == null) { + return HighlightUtil.createIncompatibleTypeHighlightInfo(selectorType, expr.getType(), label.getTextRange(), 0); + } + if (getSwitchSelectorKind(selectorType) == null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr) + .descriptionAndTooltip(JavaErrorBundle.message("constant.expression.required")).create(); + } + return null; + } + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(label) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.constant.expression.required")).create(); + } + + private void fillElementsToCheckDuplicates(@NotNull MultiMap elements, @NotNull PsiCaseLabelElement labelElement) { + if (labelElement instanceof PsiDefaultCaseLabelElement) { + elements.putValue(defaultValue, labelElement); + } + else if (labelElement instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)labelElement).resolve(); + if (element instanceof PsiEnumConstant) { + elements.putValue(((PsiEnumConstant)element).getName(), labelElement); + } + } + else if (labelElement instanceof PsiExpression) { + elements.putValue(evaluateConstant(labelElement), labelElement); + } + } + + private static void fillElementsToCheckFallThroughLegality(@NotNull MultiMap elements, + @NotNull PsiSwitchLabelStatementBase switchLabel, + @NotNull PsiCaseLabelElement labelElement) { + if (labelElement instanceof PsiPattern || labelElement instanceof PsiDefaultCaseLabelElement) { + elements.putValue(switchLabel, labelElement); + } + else if (labelElement instanceof PsiExpression) { + if (isConstantLabelElement(labelElement)) { + elements.putValue(switchLabel, labelElement); + } + } + } + + private static void fillElementsToCheckDominance(@NotNull List elements, @NotNull PsiCaseLabelElement labelElement) { + if (labelElement instanceof PsiPattern) { + elements.add(labelElement); + } + else if (labelElement instanceof PsiExpression) { + if (isNullType(labelElement) || isConstantLabelElement(labelElement)) { + elements.add(labelElement); + } + } + } + + private void checkFallThroughFromToPattern(@NotNull MultiMap elements, + @NotNull List results) { + if (elements.isEmpty()) return; + Set alreadyFallThroughElements = new HashSet<>(); + for (var entry : elements.entrySet()) { + Collection labelElements = entry.getValue(); + if (labelElements.size() <= 1) continue; + boolean existPattern = false, existsConst = false, existsDefault = false; + for (PsiCaseLabelElement currentElement : labelElements) { + if (currentElement instanceof PsiPattern) { + if (existPattern || existsConst || existsDefault) { + alreadyFallThroughElements.add(currentElement); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(currentElement) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.illegal.fall.through.to")).create()); + } + existPattern = true; + } + else if (isConstantLabelElement(currentElement)) { + if (existPattern) { + alreadyFallThroughElements.add(currentElement); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(currentElement) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.illegal.fall.through.from")).create()); + } + existsConst = true; + } + else if (currentElement instanceof PsiDefaultCaseLabelElement) { + if (existPattern) { + alreadyFallThroughElements.add(currentElement); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(currentElement) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.illegal.fall.through.from")).create()); + } + existsDefault = true; + } + } + } + checkFallThroughInSwitchStatement(elements, results, alreadyFallThroughElements); + } + + private void checkFallThroughInSwitchStatement(@NotNull MultiMap elements, + @NotNull List results, + @NotNull Set alreadyFallThroughElements) { + if (!(myBlock instanceof PsiSwitchStatement)) return; + var elementsIterator = elements.entrySet().iterator(); + // skip first switch label + elementsIterator.next(); + while (elementsIterator.hasNext()) { + var entry = elementsIterator.next(); + PsiSwitchLabelStatementBase switchLabel = entry.getKey(); + if (!(switchLabel instanceof PsiSwitchLabelStatement)) return; + var patternElements = ContainerUtil.filter(entry.getValue(), labelElement -> labelElement instanceof PsiPattern); + if (patternElements.isEmpty()) continue; + PsiStatement lastStatement = PsiTreeUtil.getPrevSiblingOfType(switchLabel, PsiStatement.class); + if (lastStatement == null) continue; + if (ControlFlowUtils.statementMayCompleteNormally(lastStatement)) { + patternElements.stream().filter(patternElement -> !alreadyFallThroughElements.contains(patternElement)).forEach(patternElement -> { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(patternElement) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.illegal.fall.through.to")).create()); + }); + } + } + } + + private static void checkDominance(@NotNull List switchLabels, @NotNull PsiType selectorType, + @NotNull List results) { + Set alreadyDominatedLabels = new HashSet<>(); + for (int i = 0; i < switchLabels.size() - 1; i++) { + PsiPattern currPattern = ObjectUtils.tryCast(switchLabels.get(i), PsiPattern.class); + if (currPattern == null) continue; + if (alreadyDominatedLabels.contains(currPattern)) continue; + for (int j = i + 1; j < switchLabels.size(); j++) { + PsiCaseLabelElement next = switchLabels.get(j); + // todo dominating pattern over const expr, although there is a contradiction with spec + if (isNullType(next) && JavaPsiPatternUtil.isTotalForType(currPattern, selectorType)) { + alreadyDominatedLabels.add(next); + continue; + } + PsiPattern nextPattern = ObjectUtils.tryCast(next, PsiPattern.class); + if (nextPattern == null) continue; + if (JavaPsiPatternUtil.dominates(currPattern, nextPattern)) { + alreadyDominatedLabels.add(next); + } + } + } + alreadyDominatedLabels.forEach(labelElement -> results.add( + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelElement) + .descriptionAndTooltip(JavaErrorBundle.message("switch.17.dominance.of.preceding.label")).create())); + } + + // todo old code, rewrite according the spec + private void checkCompleteness(@NotNull PsiExpression selectorExpression, @NotNull PsiType selectorType, + @NotNull MultiMap elements, @NotNull List results, + boolean hasDefaultCase) { + if (!(myBlock instanceof PsiSwitchExpression) && !(myBlock instanceof PsiSwitchStatement && isEnhancedSwitch())) return; + Set missingConstants = new HashSet<>(); + boolean exhaustive = hasDefaultCase; + if (!exhaustive) { + if (!elements.isEmpty() && selectorType instanceof PsiClassType) { + PsiClass type = ((PsiClassType)selectorType).resolve(); + if (type != null && type.isEnum()) { + for (PsiField field : type.getFields()) { + if (field instanceof PsiEnumConstant && !elements.containsKey(field.getName())) { + missingConstants.add(field.getName()); + } + } + exhaustive = missingConstants.isEmpty(); + } + } + } + if (!exhaustive) { + PsiElement range = ObjectUtils.notNull(selectorExpression, myBlock); + String message = JavaErrorBundle.message(elements.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); + if (!missingConstants.isEmpty()) { + QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddMissingEnumBranchesFix(myBlock, missingConstants)); + } + QuickFixAction.registerQuickFixAction(info, getFixFactory().createAddSwitchDefaultFix(myBlock, null)); + results.add(info); + } + } + + private boolean coversType(@NotNull PsiType type) { + PsiCodeBlock body = myBlock.getBody(); + if (body == null) return false; + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase)) continue; + PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; + if (labelStatement.isDefaultCase()) return true; + PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList(); + if (labelElementList == null) continue; + for (PsiCaseLabelElement element : labelElementList.getElements()) { + if (element instanceof PsiDefaultCaseLabelElement) return true; + if (element instanceof PsiPattern && JavaPsiPatternUtil.isTotalForType(((PsiPattern)element), type)) { + return true; + } + } + } + return false; + } + + private static boolean isNullType(@NotNull PsiElement element) { + return element instanceof PsiExpression && TypeConversionUtil.isNullType(((PsiExpression)element).getType()); + } + + private static boolean isConstantLabelElement(@NotNull PsiCaseLabelElement labelElement) { + return evaluateConstant(labelElement) != null || isEnumConstant(labelElement); + } + + private static boolean isEnumConstant(@NotNull PsiCaseLabelElement element) { + if (element instanceof PsiReferenceExpression) { + PsiElement resolved = ((PsiReferenceExpression)element).resolve(); + return resolved instanceof PsiEnumConstant; + } + return false; + } + + @Nullable + private static Object evaluateConstant(@NotNull PsiCaseLabelElement constant) { + return JavaPsiFacade.getInstance(constant.getProject()).getConstantEvaluationHelper().computeConstantExpression(constant, false); + } + + private boolean isEnhancedSwitch() { + PsiExpression selector = myBlock.getExpression(); + if (selector == null) return false; + PsiType selectorType = selector.getType(); + if (selectorType == null || getSwitchSelectorKind(selectorType) != null) return false; + PsiCodeBlock body = myBlock.getBody(); + if (body == null) return false; + return ContainerUtil.exists(body.getStatements(), st -> st instanceof PsiPattern || isNullType(st)); + } +} + diff --git a/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiPatternUtil.java b/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiPatternUtil.java index b5d613797841..e332ee190ea2 100644 --- a/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiPatternUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/util/JavaPsiPatternUtil.java @@ -75,6 +75,89 @@ public final class JavaPsiPatternUtil { return pattern; } + @Contract(value = "null -> null", pure = true) + @Nullable + public static PsiType getPatternType(@Nullable PsiPattern pattern) { + if (pattern == null) return null; + if (pattern instanceof PsiGuardedPattern) { + return getPatternType(((PsiGuardedPattern)pattern).getPrimaryPattern()); + } + else if (pattern instanceof PsiParenthesizedPattern) { + return getPatternType(((PsiParenthesizedPattern)pattern).getPattern()); + } + else if (pattern instanceof PsiTypeTestPattern) { + PsiTypeElement checkType = ((PsiTypeTestPattern)pattern).getCheckType(); + if (checkType != null) return checkType.getType(); + } + return null; + } + + /** + * 14.30.3 Pattern Totality and Dominance + * http://cr.openjdk.java.net/~gbierman/jep406/jep406-20210527/specs/patterns-switch-jls.html#jls-14.30.3 + */ + @Contract(value = "null, _ -> false", pure = true) + public static boolean isTotalForType(@Nullable PsiPattern pattern, @NotNull PsiType type) { + if (pattern == null) return false; + if (pattern instanceof PsiGuardedPattern) { + PsiGuardedPattern guarded = (PsiGuardedPattern)pattern; + Object constVal = evaluateConstant(guarded.getGuardingExpression()); + return isTotalForType(guarded.getPrimaryPattern(), type) && Boolean.TRUE.equals(constVal); + } + else if (pattern instanceof PsiParenthesizedPattern) { + return isTotalForType(((PsiParenthesizedPattern)pattern).getPattern(), type); + } + else if (pattern instanceof PsiTypeTestPattern) { + PsiTypeElement checkType = ((PsiTypeTestPattern)pattern).getCheckType(); + if (checkType == null) return false; + PsiClass baseClass = PsiTypesUtil.getPsiClass(TypeConversionUtil.erasure(checkType.getType())); + PsiClass typeClass = PsiTypesUtil.getPsiClass(TypeConversionUtil.erasure(type)); + return typeClass != null && baseClass != null && InheritanceUtil.isInheritorOrSelf(typeClass, baseClass, true); + } + return false; + } + + /** + * 14.30.3 Pattern Totality and Dominance + * http://cr.openjdk.java.net/~gbierman/jep406/jep406-20210527/specs/patterns-switch-jls.html#jls-14.30.3 + */ + @Contract(value = "null, _ -> false", pure = true) + public static boolean dominates(@Nullable PsiPattern who, @NotNull PsiPattern overWhom) { + if (who == null) return false; + if (overWhom instanceof PsiGuardedPattern) { + if (who instanceof PsiTypeTestPattern) { + PsiType whoType = getPatternType(who); + PsiType overWhomType = getPatternType(overWhom); + if (whoType != null && overWhomType != null && whoType.equalsToText(overWhomType.getCanonicalText())) { + return true; + } + } + else if (who instanceof PsiParenthesizedPattern) { + return dominates(((PsiParenthesizedPattern)who).getPattern(), overWhom); + } + else if (who instanceof PsiGuardedPattern) { + boolean dominates = dominates(((PsiGuardedPattern)who).getPrimaryPattern(), overWhom); + if (!dominates) return false; + Object constVal = evaluateConstant(((PsiGuardedPattern)who).getGuardingExpression()); + return Boolean.TRUE.equals(constVal); + } + else { + return false; + } + return dominates(who, ((PsiGuardedPattern)overWhom).getPrimaryPattern()); + } + else if (overWhom instanceof PsiParenthesizedPattern) { + PsiPattern pattern = ((PsiParenthesizedPattern)overWhom).getPattern(); + if (pattern == null) return false; + return dominates(who, pattern); + } + else if (overWhom instanceof PsiTypeTestPattern) { + PsiType overWhomType = getPatternType(overWhom); + return overWhomType != null && isTotalForType(who, overWhomType); + } + return false; + } + private static void collectPatternVariableCandidates(@NotNull PsiExpression scope, @NotNull PsiExpression expression, Collection candidates, boolean strict) { while (true) { @@ -108,4 +191,11 @@ public final class JavaPsiPatternUtil { } } } + + @Nullable + private static Object evaluateConstant(@Nullable PsiExpression expression) { + if (expression == null) return null; + return JavaPsiFacade.getInstance(expression.getProject()).getConstantEvaluationHelper() + .computeConstantExpression(expression, false); + } } diff --git a/java/java-psi-impl/src/messages/JavaErrorBundle.properties b/java/java-psi-impl/src/messages/JavaErrorBundle.properties index 1f5433d14f30..bf1aff8bb251 100644 --- a/java/java-psi-impl/src/messages/JavaErrorBundle.properties +++ b/java/java-psi-impl/src/messages/JavaErrorBundle.properties @@ -219,9 +219,13 @@ not.a.statement=Not a statement invalid.statement=Invalid statement incompatible.types=Incompatible types. Found: ''{1}'', required: ''{0}'' incompatible.types.reason.ambiguous.method.reference=
reason: method reference is ambiguous: both ''{0}'' and ''{1}'' match +incompatible.switch.17.null.type=''{0}'' cannot be converted to ''{1}'' inaccessible.type=''{0}'' is inaccessible here valid.switch.selector.types=byte, char, short or int valid.switch.17.selector.types=char, byte, short, int, Character, Byte, Short, Integer, String, or an enum +switch.17.illegal.fall.through.from=Illegal fall-through from a pattern +switch.17.illegal.fall.through.to=Illegal fall-through to a pattern +switch.17.dominance.of.preceding.label=This case label is dominated by a preceding case label dot.expected.after.super.or.this='.' expected unqualified.super.disallowed=Unqualified super reference is not allowed in extension method static.interface.method.call.qualifier=Static method may be invoked on containing interface class only @@ -237,6 +241,7 @@ array.initializer.not.allowed=Array initializer is not allowed here case.statement.outside.switch=Case statement outside switch qualified.enum.constant.in.switch=An enum switch case label must be the unqualified name of an enumeration constant constant.expression.required=Constant expression required +switch.17.constant.expression.required=Constant expression, pattern or null is required duplicate.default.switch.label=Duplicate default label duplicate.switch.label=Duplicate label ''{0}'' switch.expr.empty='switch' expression does not have any case clauses diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java index c3b357e9bc9c..af0d96bf124e 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingInSwitchExpr.java @@ -1,37 +1,485 @@ +import java.util.*; + class Main { static class X { int f() { return 0; } } - int switchTest(Object o) { - int i1 = switch(o) { + int switchTestResolve(Object o) { + int i1 = switch(o) { case X x -> x.f(); default -> 1; }; - int i2 = switch(o) { + int i2 = switch(o) { case null, X x -> x.f(); default -> 1; }; - int i3 = switch(o) { + int i3 = switch(o) { case X x, null -> x.f(); default -> 1; }; - int i4 = switch(o) { - case String s, X x -> x.f(); + int i4 = switch(o) { + case String s, X x -> x.f(); default -> 1; }; - int i5 = switch(o) { - case String s, X x -> x.f(); + int i5 = switch(o) { + case String s, X x -> x.f(); default -> 1; }; - int i6 = switch(o) { - case X x, String s -> x.f(); + int i6 = switch(o) { + case X x, String s -> x.f(); default -> 1; }; return i1 + i2 + i3 + i4 + i5 + i6; } + + void constLabelAndSelectorCompatibility(Number n, CharSequence c, Integer i, String s) { + switch (n) { + case 1: + System.out.println("ok"); + } + String str; + str = switch (n) { + case 1 -> "ok"; + default -> "not ok"; + }; + + switch (c) { + case "ok": + System.out.println("ok"); + } + str = switch (c) { + case "ok" -> "ok"; + default -> "not ok"; + }; + + switch (i) { + case 1: + System.out.println("ok"); + } + str= switch (i) { + case 1 -> "ok"; + default -> "not ok"; + }; + + switch (s) { + case "null" : + System.out.println("null"); + break; + default: + System.out.println("s"); + } + str = switch (s) { + case "null" -> "null"; + default -> "s"; + }; + } + + void incompatibleNullLabelAndSelector(int i) { + switch (i) { + case null: + System.out.println("ok"); + } + String str; + str = switch (i) { + case null -> "ok"; + default -> "not ok"; + }; + } + + void defaultAlwaysCompatible(int i) { + switch (i) { + case 1, default: + System.out.println("ok"); + } + String str; + str = switch (i) { + case 1, default -> "ok"; + }; + } + + void patternsCompatibilty(I i) { + switch (i) { + case Sub1 s2: + System.out.println("s1"); + break; + case Sub5 s5: + System.out.println("s5"); + default: + System.out.println("s"); + } + String str; + str = switch (i) { + case Sub1 s2 -> "s1"; + case Sub5 s5 -> "s5"; + default -> "s"; + }; + + switch (i) { + // total pattern + case Object o: + System.out.println("s1"); + } + str = switch (i) { + // total pattern + case Object o -> "s1"; + }; + } + + void duplicateLabels(Integer i) { + // A switch label may not use more than one default label + switch (i) { + case 1, default: + System.out.println("s1"); + break; + default: + System.out.println("s"); + } + String str; + str = switch (i) { + case 1, default -> "s1"; + default -> "s"; + }; + + switch (i) { + case default: + System.out.println("s1"); + break; + case default: + System.out.println("s"); + } + str = switch (i) { + case default -> "s1"; + case default -> "s"; + }; + + // A switch label may not have more than one default case label element + switch (i) { + case default, default: + System.out.println("s"); + } + str = switch (i) { + case default, default -> "s"; + }; + + // A switch label may not have more than one null case label element. + switch (i) { + case 1, null: + System.out.println("s"); + case null: + System.out.println("null"); + } + str = switch (i) { + case 1, null -> "s"; + case null -> "null"; + }; + } + + void fallThroughToPatterns(Object o, Integer ii) { + /* wasn't implemented in javac + If a switch label has a null case label element then if the switch label also has any pattern case element labels, t + they must be type patterns (14.30.1). + */ + // A switch label may not have more than one pattern case label element. + switch (o) { + case Integer i, Long l && l != null: System.out.println("s"); + default: System.out.println("null"); + } + String str; + str = switch (o) { + case Integer i, Long l && l != null -> "s"; + default -> "null"; + }; + // todo A switch label may not have both a pattern case label element and a default case label element. + // A switch label may not have both a pattern case label element and a default case label element. + switch (o) { + case Integer i, default: System.out.println("s"); + } + str = switch (o) { + case Integer i, default -> "s"; + }; + switch (o) { + case default, Integer i: System.out.println("s"); + } + str = switch (o) { + case default, Integer i -> "s"; + }; + + // If a switch label has a constant case label element then if the switch label also has other case element labels + // they must be either a constant case label element, the default case label element, or the null case label element. + switch (ii) { + case 1, Integer i1 && i1 > 5: + System.out.println("s1"); + break; + default: System.out.println("null"); + } + str = switch (ii) { + case 1, Integer i1 && i1 > 5 -> "s1"; + default -> "null"; + }; + switch (ii) { + case Integer i1 && i1 > 5, 1: + System.out.println("s1"); + break; + default: System.out.println("null"); + } + str = switch (ii) { + case Integer i1 && i1 > 5, 1 -> "s1"; + default -> "null"; + }; + // more complex case + switch (ii) { + case 1, null, Integer i1 && i1 > 5, default: + System.out.println("s1"); + break; + } + str = switch (ii) { + case 1, null, Integer i1 && i1 > 5, default -> "s1"; + }; + + /** + * It is a compile-time error if there is a statement in a switch block that consists of switch-labeled statement groups + * for which both of the following are true: + * It is labeled with a switch label that has a pattern case label element whose pattern introduces a pattern variable. + * There is a statement preceding it in the switch block and that statement can completely normally (14.22). + */ + switch (o) { + case default: + System.out.println("def"); + case Float d: + System.out.println("float"); + } + switch (o) { + case null, Integer i: + if (o != null) { + throw new IllegalArgumentException(""); + } + case Float d: + System.out.println("float"); + } + switch (o) { + case null: + if (o != null) { + throw new IllegalArgumentException(""); + } + break; + case Float d: + System.out.println("float"); + } + } + + void dominance(Object o, Integer ii) { + // A switch label that has a pattern case label element p dominates another switch label that has a pattern case label element q if p dominates q + switch (o) { + case List n: + System.out.println("num"); + break; + case List i: + System.out.println("int"); + break; + default: + System.out.println("def"); + break; + } + String str; + str = switch (o) { + case List n -> "num"; + case List i -> "int"; + default -> "def"; + }; + + switch (o) { + case Number n: + System.out.println("num"); + break; + case Integer i: + System.out.println("int"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case Number n -> "num"; + case Integer i -> "int"; + default -> "def"; + }; + + // Dominance permits a guarded pattern to be followed by its unguarded form: + switch (o) { + case Integer o1 && o1 != null: + System.out.println("num"); + break; + case Integer i: + System.out.println("int"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case Integer o1 && o1 != null -> "num"; + case Integer i -> "int"; + default -> "def"; + }; + + switch (o) { + case (Integer i): + System.out.println("int"); + break; + case Integer o1 && o1 != null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case (Integer i) -> "num"; + case Integer o1 && o1 != null -> "int"; + default -> "def"; + }; + + switch (o) { + case (Integer o1 && o1 > 5): + System.out.println("int"); + break; + case Integer o2 && o2 != null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case (Integer o1 && o1 > 5) -> "num"; + case Integer o2 && o2 != null -> "int"; + default -> "def"; + }; + + switch (o) { + case (Number i && false): + System.out.println("int"); + break; + case Integer o2 && o2 != null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case (Number i && false) -> "num"; + case Integer o2 && o2 != null -> "int"; + default -> "def"; + }; + + switch (o) { + case (Integer i && true): + System.out.println("int"); + break; + case (Integer o2 && o2 != null): + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (o) { + case (Integer i && true) -> "num"; + case (Integer o2 && o2 != null) -> "int"; + default -> "def"; + }; + + // A switch label that has a pattern case label element p that is total for the type of the selector expression + // of the enclosing switch statement or switch expression dominates a switch label that has a null case label element. + switch (ii) { + case Object obj: + System.out.println("int"); + break; + case null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (ii) { + case Object obj -> "num"; + case null -> "int"; + default -> "def"; + }; + + switch (ii) { + case Object obj, null: + System.out.println("int"); + break; + default: + System.out.println("def"); + break; + } + str = switch (ii) { + case Object obj, null -> "int"; + default -> "def"; + }; + + switch (ii) { + case (Integer i && true): + System.out.println("int"); + break; + case null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (ii) { + case (Integer i && true) -> "int"; + case null -> "int"; + default -> "def"; + }; + + switch (ii) { + case ((Integer i && false)): + System.out.println("int"); + break; + case null: + System.out.println("num"); + break; + default: + System.out.println("def"); + break; + } + str = switch (ii) { + case ((Integer i && false)) -> "int"; + case null -> "int"; + default -> "def"; + }; + } } + +sealed interface I permits Sub1, Sub2, Sub3 { +} + +enum Days { + MONDAY, TUESDAY, WEDNESDAY +} + +final class Sub1 implements I { +} + +final class Sub2 implements I { +} + +sealed class Sub3 implements I permits Sub4 { +} + +final class Sub4 extends Sub3 { +} + +final class Sub5 {} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingWithGuard.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingWithGuard.java index c33cbbdcb641..eb1516240701 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingWithGuard.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingPatternsInSwitch/PatternMatchingWithGuard.java @@ -2,13 +2,13 @@ class Main { static int m(Object o) { - int i1 = switch(o) { + int i1 = switch(o) { case String s && s.length() > 0 -> s.length(); case String s -> s.length(); default -> 1; }; - int i2 = switch(o) { + int i2 = switch(o) { case String s && (s.length() > 0) -> s.length(); case String s -> s.length(); default -> 1;