IDEA-270438 - initial highlighting for switch patterns

Includes dominance, fall-through rules, completeness doesn't work, new quick fixes doesn't exist

GitOrigin-RevId: dcdd00ee8204fac5d9d172f5e71281887645cccd
This commit is contained in:
Ilyas Selimov
2021-06-23 10:21:25 +00:00
committed by intellij-monorepo-bot
parent d6123361e1
commit 6ac057e620
7 changed files with 1195 additions and 230 deletions
@@ -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<HighlightInfo> checkSwitchExpressionReturnTypeCompatible(@NotNull PsiSwitchExpression switchExpression) {
if (!PsiPolyExpressionUtil.isPolyExpression(switchExpression)) return Collections.emptyList();
@@ -2168,108 +2124,6 @@ public final class HighlightUtil {
return null;
}
@NotNull
static Collection<HighlightInfo> 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<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;
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<Object, Collection<PsiElement>> 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<String> 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<HighlightInfo> 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)) {
@@ -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
@@ -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<HighlightInfo> checkSwitchBlockStatements();
@NotNull
abstract List<HighlightInfo> checkSwitchSelectorType();
@NotNull
abstract List<HighlightInfo> checkSwitchLabelValues();
QuickFixFactory getFixFactory() {
return QuickFixFactory.getInstance();
}
@NotNull
List<HighlightInfo> 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<Object, PsiElement> values, @NotNull List<HighlightInfo> results) {
for (Map.Entry<Object, Collection<PsiElement>> 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<HighlightInfo> 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<HighlightInfo> 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<HighlightInfo> checkSwitchLabelValues() {
PsiCodeBlock body = myBlock.getBody();
if (body == null) return Collections.emptyList();
PsiExpression selectorExpression = myBlock.getExpression();
PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType();
MultiMap<Object, PsiElement> values = new MultiMap<>();
List<HighlightInfo> 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<String> 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<HighlightInfo> checkSwitchBlockStatements() {
return defaultModel.checkSwitchBlockStatements();
}
@NotNull
@Override
List<HighlightInfo> 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<HighlightInfo> 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<Object, PsiElement>();
var elementsToCheckFallThroughLegality = new MultiMap<PsiSwitchLabelStatementBase, PsiCaseLabelElement>(new LinkedHashMap<>());
List<PsiCaseLabelElement> elementsToCheckDominance = new ArrayList<>();
List<HighlightInfo> 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<Object, PsiElement> 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<PsiSwitchLabelStatementBase, PsiCaseLabelElement> 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<PsiCaseLabelElement> 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<PsiSwitchLabelStatementBase, PsiCaseLabelElement> elements,
@NotNull List<HighlightInfo> results) {
if (elements.isEmpty()) return;
Set<PsiCaseLabelElement> alreadyFallThroughElements = new HashSet<>();
for (var entry : elements.entrySet()) {
Collection<PsiCaseLabelElement> 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<PsiSwitchLabelStatementBase, PsiCaseLabelElement> elements,
@NotNull List<HighlightInfo> results,
@NotNull Set<PsiCaseLabelElement> 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<PsiCaseLabelElement> switchLabels, @NotNull PsiType selectorType,
@NotNull List<HighlightInfo> results) {
Set<PsiCaseLabelElement> 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<Object, PsiElement> elements, @NotNull List<HighlightInfo> results,
boolean hasDefaultCase) {
if (!(myBlock instanceof PsiSwitchExpression) && !(myBlock instanceof PsiSwitchStatement && isEnhancedSwitch())) return;
Set<String> 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));
}
}
@@ -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<PsiPatternVariable> 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);
}
}
@@ -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=<br/>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
@@ -1,37 +1,485 @@
import java.util.*;
class Main {
static class X {
int f() { return 0; }
}
int switchTest(Object o) {
int i1 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
int switchTestResolve(Object o) {
int i1 = switch(o) {
case X x -> x.f();
default -> 1;
};
int i2 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
int i2 = switch(o) {
case null, X x -> x.f();
default -> 1;
};
int i3 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
int i3 = switch(o) {
case X x, null -> x.f();
default -> 1;
};
int i4 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
case String s, X x -> x.f();
int i4 = switch(o) {
case String s, <error descr="Illegal fall-through to a pattern">X x</error> -> x.f();
default -> 1;
};
int i5 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
case String s, X x -> x.f();
int i5 = switch(o) {
case String s, <error descr="Illegal fall-through to a pattern">X x</error> -> x.f();
default -> 1;
};
int i6 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
case X x, String s -> x.f();
int i6 = switch(o) {
case X x, <error descr="Illegal fall-through to a pattern">String s</error> -> x.f();
default -> 1;
};
return i1 + i2 + i3 + i4 + i5 + i6;
}
void constLabelAndSelectorCompatibility(Number n, CharSequence c, Integer i, String s) {
switch (n) {
case <error descr="Incompatible types. Found: 'int', required: 'java.lang.Number'">1</error>:
System.out.println("ok");
}
String str;
str = switch (n) {
case <error descr="Incompatible types. Found: 'int', required: 'java.lang.Number'">1</error> -> "ok";
default -> "not ok";
};
switch (c) {
case <error descr="Incompatible types. Found: 'java.lang.String', required: 'java.lang.CharSequence'">"ok"</error>:
System.out.println("ok");
}
str = switch (c) {
case <error descr="Incompatible types. Found: 'java.lang.String', required: 'java.lang.CharSequence'">"ok"</error> -> "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 <error descr="'null' cannot be converted to 'int'">null</error>:
System.out.println("ok");
}
String str;
str = switch (i) {
case <error descr="'null' cannot be converted to 'int'">null</error> -> "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 <error descr="Incompatible types. Found: 'Sub5', required: 'I'">Sub5 s5</error>:
System.out.println("s5");
default:
System.out.println("s");
}
String str;
str = switch (i) {
case Sub1 s2 -> "s1";
case <error descr="Incompatible types. Found: 'Sub5', required: 'I'">Sub5 s5</error> -> "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, <error descr="Duplicate default label">default</error>:
System.out.println("s1");
break;
<error descr="Duplicate default label">default</error>:
System.out.println("s");
}
String str;
str = switch (i) {
case 1, <error descr="Duplicate default label">default</error> -> "s1";
<error descr="Duplicate default label">default</error> -> "s";
};
switch (i) {
case <error descr="Duplicate default label">default</error>:
System.out.println("s1");
break;
case <error descr="Duplicate default label">default</error>:
System.out.println("s");
}
str = switch (i) {
case <error descr="Duplicate default label">default</error> -> "s1";
case <error descr="Duplicate default label">default</error> -> "s";
};
// A switch label may not have more than one default case label element
switch (i) {
case <error descr="Duplicate default label">default</error>, <error descr="Duplicate default label">default</error>:
System.out.println("s");
}
str = switch (i) {
case <error descr="Duplicate default label">default</error>, <error descr="Duplicate default label">default</error> -> "s";
};
// A switch label may not have more than one null case label element.
switch (i) {
case 1, <error descr="Duplicate label 'null'">null</error>:
System.out.println("s");
case <error descr="Duplicate label 'null'">null</error>:
System.out.println("null");
}
str = switch (i) {
case 1, <error descr="Duplicate label 'null'">null</error> -> "s";
case <error descr="Duplicate label 'null'">null</error> -> "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, <error descr="Illegal fall-through to a pattern">Long l && l != null</error>: System.out.println("s");
default: System.out.println("null");
}
String str;
str = switch (o) {
case Integer i, <error descr="Illegal fall-through to a pattern">Long l && l != null</error> -> "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, <error descr="Illegal fall-through from a pattern">default</error>: System.out.println("s");
}
str = switch (o) {
case Integer i, <error descr="Illegal fall-through from a pattern">default</error> -> "s";
};
switch (o) {
case default, <error descr="Illegal fall-through to a pattern">Integer i</error>: System.out.println("s");
}
str = switch (o) {
case default, <error descr="Illegal fall-through to a pattern">Integer i</error> -> "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, <error descr="Illegal fall-through to a pattern">Integer i1 && i1 > 5</error>:
System.out.println("s1");
break;
default: System.out.println("null");
}
str = switch (ii) {
case 1, <error descr="Illegal fall-through to a pattern">Integer i1 && i1 > 5</error> -> "s1";
default -> "null";
};
switch (ii) {
case Integer i1 && i1 > 5, <error descr="Illegal fall-through from a pattern">1</error>:
System.out.println("s1");
break;
default: System.out.println("null");
}
str = switch (ii) {
case Integer i1 && i1 > 5, <error descr="Illegal fall-through from a pattern">1</error> -> "s1";
default -> "null";
};
// more complex case
switch (ii) {
case 1, null, <error descr="Illegal fall-through to a pattern">Integer i1 && i1 > 5</error>, <error descr="Illegal fall-through from a pattern">default</error>:
System.out.println("s1");
break;
}
str = switch (ii) {
case 1, null, <error descr="Illegal fall-through to a pattern">Integer i1 && i1 > 5</error>, <error descr="Illegal fall-through from a pattern">default</error> -> "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 <error descr="Illegal fall-through to a pattern">Float d</error>:
System.out.println("float");
}
switch (o) {
case null, Integer i:
if (o != null) {
throw new IllegalArgumentException("");
}
case <error descr="Illegal fall-through to a pattern">Float d</error>:
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 <error descr="This case label is dominated by a preceding case label">List i</error>:
System.out.println("int");
break;
default:
System.out.println("def");
break;
}
String str;
str = switch (o) {
case List n -> "num";
case <error descr="This case label is dominated by a preceding case label">List i</error> -> "int";
default -> "def";
};
switch (o) {
case Number n:
System.out.println("num");
break;
case <error descr="This case label is dominated by a preceding case label">Integer i</error>:
System.out.println("int");
break;
default:
System.out.println("def");
break;
}
str = switch (o) {
case Number n -> "num";
case <error descr="This case label is dominated by a preceding case label">Integer i</error> -> "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 <error descr="This case label is dominated by a preceding case label">Integer o1 && o1 != null</error>:
System.out.println("num");
break;
default:
System.out.println("def");
break;
}
str = switch (o) {
case (Integer i) -> "num";
case <error descr="This case label is dominated by a preceding case label">Integer o1 && o1 != null</error> -> "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 <error descr="This case label is dominated by a preceding case label">(Integer o2 && o2 != null)</error>:
System.out.println("num");
break;
default:
System.out.println("def");
break;
}
str = switch (o) {
case (Integer i && true) -> "num";
case <error descr="This case label is dominated by a preceding case label">(Integer o2 && o2 != null)</error> -> "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 <error descr="This case label is dominated by a preceding case label">null</error>:
System.out.println("num");
break;
default:
System.out.println("def");
break;
}
str = switch (ii) {
case Object obj -> "num";
case <error descr="This case label is dominated by a preceding case label">null</error> -> "int";
default -> "def";
};
switch (ii) {
case Object obj, <error descr="This case label is dominated by a preceding case label">null</error>:
System.out.println("int");
break;
default:
System.out.println("def");
break;
}
str = switch (ii) {
case Object obj, <error descr="This case label is dominated by a preceding case label">null</error> -> "int";
default -> "def";
};
switch (ii) {
case (Integer i && true):
System.out.println("int");
break;
case <error descr="This case label is dominated by a preceding case label">null</error>:
System.out.println("num");
break;
default:
System.out.println("def");
break;
}
str = switch (ii) {
case (Integer i && true) -> "int";
case <error descr="This case label is dominated by a preceding case label">null</error> -> "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 {}
@@ -2,13 +2,13 @@
class Main {
static int m(Object o) {
int i1 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
int i1 = switch(o) {
case String s && s.length() > 0 -> s.length();
case String s -> s.length();
default -> 1;
};
int i2 = switch(<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">o</error>) {
int i2 = switch(o) {
case String s && (s.length() > 0) -> s.length();
case String s -> s.length();
default -> 1;