[java-highlighting] checkLabelSelectorCompatibility extracted and moved to SwitchChecker

Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: bec076c5bff67e22d06e64e573d3219a4c89dd4d
This commit is contained in:
Tagir Valeev
2025-02-19 17:57:38 +00:00
committed by intellij-monorepo-bot
parent 7bb33c7a68
commit 9e458c909c
23 changed files with 278 additions and 497 deletions
@@ -310,7 +310,7 @@ statement.unreachable=Unreachable statement
statement.unreachable.loop.body=Loop condition is always false making the loop body unreachable
switch.rule.should.produce.result=Switch expression rule should produce a result in all execution paths
switch.expression.no.result=Switch expression does not have any result expressions
switch.expression.no.result='switch' expression does not have any result expressions
switch.expression.should.produce.result=Switch expression should produce a result in all execution paths
switch.expression.incompatible.type=Bad type in switch expression: {0} cannot be converted to {1}
switch.expression.cannot.be.void=Target type for switch expression cannot be void
@@ -318,6 +318,12 @@ switch.label.expected=Statement must be prepended with a case label
switch.different.case.kinds=Different 'case' kinds used in 'switch'
switch.selector.type.invalid=Selector type of ''{0}'' is not supported
switch.selector.type.invalid.level=Selector type of ''{0}'' is not supported at language level ''{1}''
switch.null.type.incompatible=''null'' cannot be converted to ''{0}''
switch.label.qualified.enum=An enum switch case label must be the unqualified name of an enumeration constant
switch.label.constant.expected=Constant expression required
switch.label.pattern.expected=Pattern expected for switch selector type ''{0}''
switch.label.unexpected=Constant expression, pattern or null is required
switch.default.label.contains.case=The label for the default case must only use the 'default' keyword, without 'case'
guard.misplaced=Guard is allowed after patterns only
guard.evaluated.to.false=Case label has a guard that is a constant expression with value 'false'
@@ -400,6 +406,7 @@ pattern.unsafe.cast=''{0}'' cannot be safely cast to ''{1}''
pattern.cannot.infer.type=Cannot infer pattern type: {0}
pattern.instanceof.supertype=Pattern type ''{0}'' is a supertype of expression type ''{1}''
pattern.instanceof.equals=Pattern type ''{0}'' is the same as expression type
pattern.expected.class.or.array.type=Class or array type is required
variable.must.be.final=Variable ''{0}'' is accessed from within inner class, needs to be declared final
variable.must.be.final.resource=Variable used as a try-with-resources resource should be final or effectively final
@@ -118,27 +118,31 @@ final class ExpressionChecker {
}
}
void checkAssignability(@Nullable PsiType lType,
@Nullable PsiType rType,
@Nullable PsiExpression expression,
@NotNull PsiElement elementToHighlight) {
if (lType == rType) return;
/**
* @return true if there's no assignatbility problem reported
*/
boolean checkAssignability(@Nullable PsiType lType,
@Nullable PsiType rType,
@Nullable PsiExpression expression,
@NotNull PsiElement elementToHighlight) {
if (lType == rType) return true;
if (expression == null) {
if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return;
if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return true;
}
else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression) || PsiTreeUtil.hasErrorElements(expression)) {
return;
return true;
}
if (rType == null) {
rType = expression.getType();
}
if (lType == null || lType == PsiTypes.nullType()) {
return;
return true;
}
if (expression != null && myVisitor.isIncompleteModel() && IncompleteModelUtil.isPotentiallyConvertible(lType, expression)) {
return;
return true;
}
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(elementToHighlight, new JavaIncompatibleTypeErrorContext(lType, rType)));
return false;
}
void checkMustBeBoolean(@NotNull PsiExpression expr) {
@@ -641,6 +641,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
private void checkSwitchBlock(@NotNull PsiSwitchBlock block) {
if (!hasErrorResults()) mySwitchChecker.checkSwitchBlockStatements(block);
if (!hasErrorResults()) mySwitchChecker.checkSwitchSelectorType(block);
if (!hasErrorResults()) mySwitchChecker.checkLabelSelectorCompatibility(block);
}
@Override
@@ -149,24 +149,24 @@ final class PatternChecker {
if (recordComponents.length == deconstructionComponents.length) {
if (isApplicableForRecordComponent(substitutedRecordComponentType, deconstructionComponentType,
JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.getMinimumLevel())) {
myVisitor.checkFeature(deconstructionComponent, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS);
myVisitor.report(JavaErrorKinds.UNSUPPORTED_FEATURE.create(deconstructionComponent, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS));
continue;
}
else if ((substitutedRecordComponentType instanceof PsiPrimitiveType ||
deconstructionComponentType instanceof PsiPrimitiveType) &&
JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(languageLevel)) {
myVisitor.report(JavaErrorKinds.CAST_INCONVERTIBLE.create(
deconstructionComponent, new JavaIncompatibleTypeErrorContext(substitutedRecordComponentType, deconstructionComponentType)));
continue;
}
if (!myVisitor.hasErrorResults()) {
if (myVisitor.isIncompleteModel() &&
(IncompleteModelUtil.hasUnresolvedComponent(substitutedRecordComponentType) ||
IncompleteModelUtil.hasUnresolvedComponent(deconstructionComponentType))) {
continue;
}
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(
deconstructionComponent, new JavaIncompatibleTypeErrorContext(substitutedRecordComponentType, deconstructionComponentType)));
if (myVisitor.isIncompleteModel() &&
(IncompleteModelUtil.hasUnresolvedComponent(substitutedRecordComponentType) ||
IncompleteModelUtil.hasUnresolvedComponent(deconstructionComponentType))) {
continue;
}
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(
deconstructionComponent, new JavaIncompatibleTypeErrorContext(substitutedRecordComponentType, deconstructionComponentType)));
}
}
else {
@@ -8,11 +8,9 @@ import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlow;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
import com.intellij.psi.util.JavaPsiSwitchUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.psi.util.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -168,7 +166,8 @@ final class SwitchChecker {
}
hasResult = hasYield(switchExpression, switchBody);
}
if (!hasResult) {
// If there are no cases, empty switch should be reported
if (!hasResult && PsiTreeUtil.getChildOfType(switchBody, PsiSwitchLabelStatementBase.class) != null) {
myVisitor.report(JavaErrorKinds.SWITCH_EXPRESSION_NO_RESULT.create(switchExpression));
}
}
@@ -262,4 +261,179 @@ final class SwitchChecker {
}
}
void checkLabelSelectorCompatibility(@NotNull PsiSwitchBlock block) {
PsiCodeBlock body = block.getBody();
if (body == null) return;
PsiExpression selector = block.getExpression();
if (selector == null) return;
PsiType selectorType = selector.getType();
if (selectorType == null) return;
boolean patterns = myVisitor.isApplicable(JavaFeature.PATTERNS_IN_SWITCH);
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
if (labelStatement.isDefaultCase()) continue;
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList == null) continue;
for (PsiCaseLabelElement label : labelElementList.getElements()) {
if (!(label instanceof PsiParenthesizedExpression) && isNullType(label)) {
if (selectorType instanceof PsiPrimitiveType && !isNullType(selector)) {
myVisitor.report(JavaErrorKinds.SWITCH_NULL_TYPE_INCOMPATIBLE.create(label, selectorType));
return;
}
return;
}
if (label instanceof PsiExpression expr) {
if (selectorType.equals(PsiTypes.nullType())) {
myVisitor.report(
JavaErrorKinds.TYPE_INCOMPATIBLE.create(expr, new JavaIncompatibleTypeErrorContext(selectorType, expr.getType())));
continue;
}
if (label instanceof PsiReferenceExpression ref) {
String enumConstName = evaluateEnumConstantName(ref);
if (enumConstName != null) {
if (!myVisitor.isApplicable(JavaFeature.ENUM_QUALIFIED_NAME_IN_SWITCH) && ref.getQualifier() != null) {
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_QUALIFIED_ENUM.create(ref));
}
continue;
}
}
if (!myVisitor.myExpressionChecker.checkAssignability(selectorType, expr.getType(), expr, expr)) continue;
}
if (patterns) {
checkLabelAndSelectorCompatibilityPattern(label, selectorType);
}
else {
checkLabelAndSelectorCompatibility(label, labelElementList, selectorType);
}
}
}
}
private static boolean isNullType(@NotNull PsiElement element) {
return element instanceof PsiExpression expression && TypeConversionUtil.isNullType(expression.getType());
}
static @Nullable PsiEnumConstant getEnumConstant(@Nullable PsiElement element) {
if (element instanceof PsiReferenceExpression referenceExpression &&
referenceExpression.resolve() instanceof PsiEnumConstant enumConstant) {
return enumConstant;
}
return null;
}
static @Nullable String evaluateEnumConstantName(@NotNull PsiReferenceExpression expr) {
PsiEnumConstant enumConstant = getEnumConstant(expr);
if (enumConstant != null) return enumConstant.getName();
return null;
}
private static @Nullable Object evaluateConstant(@NotNull PsiCaseLabelElement constant) {
return JavaPsiFacade.getInstance(constant.getProject()).getConstantEvaluationHelper().computeConstantExpression(constant, false);
}
private void checkLabelAndSelectorCompatibility(@NotNull PsiCaseLabelElement label,
@NotNull PsiCaseLabelElementList labelElementList,
@NotNull PsiType selectorType) {
if (label instanceof PsiExpression expr) {
Object value = null;
if (expr instanceof PsiReferenceExpression ref) {
String enumConstName = evaluateEnumConstantName(ref);
if (enumConstName != null) {
value = enumConstName;
}
}
if (value == null) {
value = ConstantExpressionUtil.computeCastTo(expr, selectorType);
}
if (value == null) {
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_CONSTANT_EXPECTED.create(expr));
}
}
else if (label instanceof PsiDefaultCaseLabelElement defaultElement && labelElementList.getElementCount() == 1) {
// If default is not the only case in the label, insufficient language level will be reported
// See JavaErrorVisitor#visitDefaultCaseLabelElement
myVisitor.report(JavaErrorKinds.SWITCH_DEFAULT_LABEL_CONTAINS_CASE.create(defaultElement, labelElementList));
}
else if (label instanceof PsiPattern) {
// Ignore patterns. If they appear here, insufficient language level will be reported
}
}
private void checkLabelAndSelectorCompatibilityPattern(@NotNull PsiCaseLabelElement label, @NotNull PsiType selectorType) {
if (label instanceof PsiDefaultCaseLabelElement) return;
if (label instanceof PsiExpression expr) {
Object constValue = evaluateConstant(expr);
if (constValue == null) {
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_CONSTANT_EXPECTED.create(expr));
return;
}
JavaPsiSwitchUtil.SelectorKind kind = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType);
if (kind.getFeature() == JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS) {
if ((kind == JavaPsiSwitchUtil.SelectorKind.LONG && !(constValue instanceof Long)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.DOUBLE && !(constValue instanceof Double)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.FLOAT && !(constValue instanceof Float)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.BOOLEAN && !(constValue instanceof Boolean))) {
PsiType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(selectorType);
if (unboxedType != null) {
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(
expr, new JavaIncompatibleTypeErrorContext(unboxedType, expr.getType())));
}
}
return;
}
if (ConstantExpressionUtil.computeCastTo(constValue, selectorType) == null) {
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(expr, new JavaIncompatibleTypeErrorContext(selectorType, expr.getType())));
return;
}
if (kind == JavaPsiSwitchUtil.SelectorKind.INT || kind == JavaPsiSwitchUtil.SelectorKind.STRING) {
return;
}
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_PATTERN_EXPECTED.create(expr, selectorType));
return;
}
else if (label instanceof PsiPattern) {
PsiPattern elementToReport = JavaPsiPatternUtil.getTypedPattern(label);
if (elementToReport == null) return;
PsiTypeElement typeElement = JavaPsiPatternUtil.getPatternTypeElement(elementToReport);
if (typeElement == null) return;
PsiType patternType = typeElement.getType();
boolean primitivesAllowed = myVisitor.isApplicable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS);
if (!(patternType instanceof PsiClassType) && !(patternType instanceof PsiArrayType) && !primitivesAllowed) {
if (patternType instanceof PsiPrimitiveType) {
myVisitor.checkFeature(elementToReport, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS);
return;
}
myVisitor.report(JavaErrorKinds.PATTERN_EXPECTED_CLASS_OR_ARRAY_TYPE.create(elementToReport));
return;
}
if ((!ContainerUtil.and(JavaPsiPatternUtil.deconstructSelectorType(selectorType),
type -> TypeConversionUtil.areTypesConvertible(type, patternType)) ||
// 14.30.3 A type pattern that declares a pattern variable of a reference type U is
// applicable at another reference type T if T is checkcast convertible to U (JEP 440-441)
// There is no rule that says that a reference type applies to a primitive type
(selectorType instanceof PsiPrimitiveType &&
//from JEP 455 it is allowed
myVisitor.isApplicable(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS)) && !primitivesAllowed) &&
//null type is applicable to any class type
!selectorType.equals(PsiTypes.nullType())) {
if (!IncompleteModelUtil.isIncompleteModel(label) ||
(!IncompleteModelUtil.isPotentiallyConvertible(selectorType, patternType, label))) {
if (selectorType instanceof PsiPrimitiveType && !primitivesAllowed) {
myVisitor.report(JavaErrorKinds.UNSUPPORTED_FEATURE.create(elementToReport, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS));
}
else {
myVisitor.report(JavaErrorKinds.TYPE_INCOMPATIBLE.create(
elementToReport, new JavaIncompatibleTypeErrorContext(selectorType, patternType)));
}
return;
}
}
myVisitor.myPatternChecker.checkUncheckedPatternConversion(elementToReport);
PsiDeconstructionPattern deconstructionPattern = JavaPsiPatternUtil.findDeconstructionPattern(elementToReport);
myVisitor.myPatternChecker.checkDeconstructionErrors(deconstructionPattern);
return;
}
myVisitor.report(JavaErrorKinds.SWITCH_LABEL_UNEXPECTED.create(label));
}
}
@@ -885,6 +885,8 @@ public final class JavaErrorKinds {
parameterized(PsiTypeTestPattern.class, PsiType.class, "pattern.instanceof.equals")
.withAnchor(PsiTypeTestPattern::getCheckType)
.withRawDescription((expr, context) -> message("pattern.instanceof.equals", context.getPresentableText()));
public static final Simple<PsiPattern> PATTERN_EXPECTED_CLASS_OR_ARRAY_TYPE =
error(PsiPattern.class, "pattern.expected.class.or.array.type");
public static final Simple<PsiTypeElement> INSTANCEOF_TYPE_PARAMETER = error("instanceof.type.parameter");
public static final Simple<PsiTypeElement> INSTANCEOF_ILLEGAL_GENERIC_TYPE = error("instanceof.illegal.generic.type");
@@ -940,6 +942,19 @@ public final class JavaErrorKinds {
message("switch.selector.type.invalid", formatType(expr.getType())) :
message("switch.selector.type.invalid.level", formatType(expr.getType()),
PsiUtil.getLanguageLevel(expr).getShortText()));
public static final Parameterized<PsiCaseLabelElement, PsiType> SWITCH_NULL_TYPE_INCOMPATIBLE =
parameterized(PsiCaseLabelElement.class, PsiType.class, "switch.null.type.incompatible")
.withRawDescription((label, selectorType) -> message("switch.null.type.incompatible", formatType(selectorType)));
public static final Simple<PsiReferenceExpression> SWITCH_LABEL_QUALIFIED_ENUM =
error(PsiReferenceExpression.class, "switch.label.qualified.enum");
public static final Simple<PsiExpression> SWITCH_LABEL_CONSTANT_EXPECTED =
error(PsiExpression.class, "switch.label.constant.expected");
public static final Simple<PsiCaseLabelElement> SWITCH_LABEL_UNEXPECTED = error("switch.label.unexpected");
public static final Parameterized<PsiDefaultCaseLabelElement, PsiCaseLabelElementList> SWITCH_DEFAULT_LABEL_CONTAINS_CASE =
parameterized("switch.default.label.contains.case");
public static final Parameterized<PsiExpression, PsiType> SWITCH_LABEL_PATTERN_EXPECTED =
parameterized(PsiExpression.class, PsiType.class, "switch.label.pattern.expected")
.withRawDescription((expr, type) -> message("switch.label.pattern.expected", formatType(type)));
public static final Simple<PsiReferenceExpression> EXPRESSION_EXPECTED = error("expression.expected");
public static final Parameterized<PsiReferenceExpression, PsiSuperExpression> EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD =
@@ -35,6 +35,7 @@ import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.InstanceOfUtils;
import com.siyeh.ig.psiutils.SwitchUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.Contract;
@@ -44,6 +45,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Consumer;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
public final class HighlightFixUtil {
@@ -914,6 +916,29 @@ public final class HighlightFixUtil {
}
}
static @Nullable IntentionAction createPrimitiveToBoxedPatternFix(@NotNull PsiElement anchor) {
PsiTypeElement element = null;
PsiType operandType = null;
if (anchor instanceof PsiInstanceOfExpression instanceOfExpression) {
element = InstanceOfUtils.findCheckTypeElement(instanceOfExpression);
operandType = instanceOfExpression.getOperand().getType();
}
else if (anchor instanceof PsiPattern pattern) {
element = JavaPsiPatternUtil.getPatternTypeElement(pattern);
PsiSwitchBlock block = PsiTreeUtil.getParentOfType(element, PsiSwitchBlock.class);
if (block != null) {
PsiExpression selector = block.getExpression();
if (selector != null) {
operandType = selector.getType();
}
}
}
if (element != null && operandType != null && TypeConversionUtil.isPrimitiveAndNotNull(element.getType())) {
return QuickFixFactory.getInstance().createReplacePrimitiveWithBoxedTypeAction(operandType, requireNonNull(element));
}
return null;
}
private static final class ReturnModel {
final PsiReturnStatement myStatement;
final PsiType myType;
@@ -320,8 +320,8 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitSwitchExpression(@NotNull PsiSwitchExpression expression) {
super.visitSwitchExpression(expression);
checkSwitchBlock(expression);
if (!hasErrorResults()) super.visitSwitchExpression(expression);
}
private void checkSwitchBlock(@NotNull PsiSwitchBlock switchBlock) {
@@ -411,13 +411,8 @@ final class JavaErrorFixProvider {
}
});
fix(UNSUPPORTED_FEATURE, error -> {
if (error.context() == JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS &&
error.psi() instanceof PsiInstanceOfExpression instanceOfExpression) {
PsiTypeElement element = InstanceOfUtils.findCheckTypeElement(instanceOfExpression);
PsiType operandType = instanceOfExpression.getOperand().getType();
if (element != null && operandType != null && TypeConversionUtil.isPrimitiveAndNotNull(element.getType())) {
return myFactory.createReplacePrimitiveWithBoxedTypeAction(operandType, requireNonNull(element));
}
if (error.context() == JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS) {
return HighlightFixUtil.createPrimitiveToBoxedPatternFix(error.psi());
}
return null;
});
@@ -439,7 +434,7 @@ final class JavaErrorFixProvider {
fix(PATTERN_INSTANCEOF_EQUALS, redundantInstanceOfFix);
fix(PATTERN_INSTANCEOF_SUPERTYPE, redundantInstanceOfFix);
}
private void createVariableFixes() {
fix(UNNAMED_VARIABLE_BRACKETS, error -> new NormalizeBracketsFix(error.psi()));
fix(UNNAMED_VARIABLE_WITHOUT_INITIALIZER, error -> myFactory.createAddVariableInitializerFix(error.psi()));
@@ -688,6 +683,9 @@ final class JavaErrorFixProvider {
fix(CALL_MEMBER_BEFORE_CONSTRUCTOR, qualifyFix);
fix(CLASS_OR_PACKAGE_EXPECTED, error -> myFactory.createRemoveQualifierFix(
requireNonNull(error.psi().getQualifierExpression()), error.psi(), error.context()));
fix(SWITCH_LABEL_QUALIFIED_ENUM, error -> myFactory.createDeleteFix(
requireNonNull(error.psi().getQualifier()), JavaErrorBundle.message("qualified.enum.constant.in.switch.remove.fix")));
fix(SWITCH_DEFAULT_LABEL_CONTAINS_CASE, error -> myFactory.createReplaceCaseDefaultWithDefaultFix(error.context()));
}
private void createAccessFixes() {
@@ -1,23 +1,10 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.daemon.impl.analysis;
import com.intellij.codeInsight.daemon.JavaErrorBundle;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.quickfix.AddMissingDeconstructionComponentsFix;
import com.intellij.codeInsight.daemon.impl.quickfix.AddMissingDeconstructionComponentsFix.Pattern;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.java.codeserver.core.JavaPsiSealedUtil;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.PsiClassType.ClassResolveResult;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.util.*;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
@@ -31,7 +18,6 @@ import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.stream.Collectors;
final class PatternHighlightingModel {
@@ -41,178 +27,6 @@ final class PatternHighlightingModel {
private static final int MAX_ITERATION_COVERAGE = 5_000;
private static final int MAX_GENERATED_PATTERN_NUMBER = 10;
static boolean createDeconstructionErrors(@Nullable PsiDeconstructionPattern deconstructionPattern,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (deconstructionPattern == null) return false;
PsiTypeElement typeElement = deconstructionPattern.getTypeElement();
PsiType recordType = typeElement.getType();
ClassResolveResult resolveResult =
recordType instanceof PsiClassType classType ? classType.resolveGenerics() : ClassResolveResult.EMPTY;
PsiClass recordClass = resolveResult.getElement();
if (recordClass == null || !recordClass.isRecord()) {
String message = JavaErrorBundle.message("deconstruction.pattern.requires.record", JavaHighlightUtil.formatType(recordType));
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
errorSink.accept(info);
return true;
}
if (resolveResult.getInferenceError() != null) {
String message = JavaErrorBundle.message("error.cannot.infer.pattern.type", resolveResult.getInferenceError());
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(message);
errorSink.accept(info);
return true;
}
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
PsiRecordComponent[] recordComponents = recordClass.getRecordComponents();
PsiPattern[] deconstructionComponents = deconstructionPattern.getDeconstructionList().getDeconstructionComponents();
boolean hasMismatchedPattern = false;
boolean reported = false;
for (int i = 0; i < Math.min(recordComponents.length, deconstructionComponents.length); i++) {
PsiPattern deconstructionComponent = deconstructionComponents[i];
PsiType recordComponentType = recordComponents[i].getType();
PsiType substitutedRecordComponentType = substitutor.substitute(recordComponentType);
PsiType deconstructionComponentType = JavaPsiPatternUtil.getPatternType(deconstructionComponent);
LanguageLevel languageLevel = PsiUtil.getLanguageLevel(deconstructionPattern);
if (!isApplicableForRecordComponent(substitutedRecordComponentType, deconstructionComponentType, languageLevel)) {
hasMismatchedPattern = true;
if (recordComponents.length == deconstructionComponents.length) {
HighlightInfo.Builder builder = null;
if (isApplicableForRecordComponent(substitutedRecordComponentType, deconstructionComponentType,
JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.getMinimumLevel())) {
builder = HighlightUtil.checkFeature(deconstructionComponent, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, languageLevel,
deconstructionComponent.getContainingFile());
}
else if ((substitutedRecordComponentType instanceof PsiPrimitiveType ||
deconstructionComponentType instanceof PsiPrimitiveType) &&
JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(languageLevel)) {
String message = JavaErrorBundle.message("inconvertible.type.cast",
JavaHighlightUtil.formatType(substitutedRecordComponentType), JavaHighlightUtil
.formatType(deconstructionComponentType));
builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR)
.range(deconstructionComponent)
.descriptionAndTooltip(message);
}
if (builder == null) {
if (IncompleteModelUtil.isIncompleteModel(deconstructionPattern) &&
(IncompleteModelUtil.hasUnresolvedComponent(substitutedRecordComponentType) ||
IncompleteModelUtil.hasUnresolvedComponent(deconstructionComponentType))) {
continue;
}
builder = HighlightUtil.createIncompatibleTypeHighlightInfo(substitutedRecordComponentType, deconstructionComponentType,
deconstructionComponent.getTextRange());
}
errorSink.accept(builder);
reported = true;
}
}
else {
HighlightInfo.Builder info = getUncheckedPatternConversionError(deconstructionComponent);
if (info != null) {
hasMismatchedPattern = true;
errorSink.accept(info);
reported = true;
}
}
if (recordComponents.length != deconstructionComponents.length && hasMismatchedPattern) {
break;
}
if (deconstructionComponent instanceof PsiDeconstructionPattern deconstructionComponentPattern) {
reported |= createDeconstructionErrors(deconstructionComponentPattern, errorSink);
}
}
if (recordComponents.length != deconstructionComponents.length) {
HighlightInfo.Builder
info = createIncorrectNumberOfNestedPatternsError(deconstructionPattern, deconstructionComponents, recordComponents,
!hasMismatchedPattern);
errorSink.accept(info);
return true;
}
return reported;
}
static @Nullable HighlightInfo.Builder getUncheckedPatternConversionError(@NotNull PsiPattern pattern) {
PsiType patternType = JavaPsiPatternUtil.getPatternType(pattern);
if (patternType == null) return null;
if (pattern instanceof PsiDeconstructionPattern subPattern) {
PsiJavaCodeReferenceElement element = subPattern.getTypeElement().getInnermostComponentReferenceElement();
if (element != null && element.getTypeParameterCount() == 0 && patternType instanceof PsiClassType classType) {
patternType = classType.rawType();
}
}
PsiType contextType = JavaPsiPatternUtil.getContextType(pattern);
if (contextType == null) return null;
if (contextType instanceof PsiWildcardType wildcardType) {
contextType = wildcardType.getExtendsBound();
}
if (!JavaGenericsUtil.isUncheckedCast(patternType, contextType)) return null;
String message = JavaErrorBundle.message("unsafe.cast.in.instanceof", contextType.getPresentableText(),
patternType.getPresentableText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(pattern).descriptionAndTooltip(message);
}
/**
* Checks if the given record component type is applicable for the pattern type based on the specified language level.
* For example:
* <pre><code>
* record SomeClass(RecordComponentType component)
* (a instanceof SomeClass(PatternType obj))
* </code></pre>
*
* @param recordComponentType the type of the record component
* @param patternType the type of the pattern
* @param languageLevel the language level to consider
* @return true if the record component type is applicable for the pattern type, false otherwise
*/
private static boolean isApplicableForRecordComponent(@NotNull PsiType recordComponentType,
@Nullable PsiType patternType,
@NotNull LanguageLevel languageLevel) {
if ((recordComponentType instanceof PsiPrimitiveType || patternType instanceof PsiPrimitiveType) &&
!JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(languageLevel)) {
return recordComponentType.equals(patternType);
}
return patternType != null && TypeConversionUtil.areTypesConvertible(recordComponentType, patternType);
}
private static @NotNull HighlightInfo.Builder createIncorrectNumberOfNestedPatternsError(@NotNull PsiDeconstructionPattern deconstructionPattern,
PsiPattern @NotNull [] patternComponents,
PsiRecordComponent @NotNull [] recordComponents,
boolean needQuickFix) {
assert patternComponents.length != recordComponents.length;
String message = JavaErrorBundle.message("incorrect.number.of.nested.patterns", recordComponents.length, patternComponents.length);
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).description(message).escapedToolTip(message);
PsiDeconstructionList deconstructionList = deconstructionPattern.getDeconstructionList();
if (needQuickFix) {
if (patternComponents.length < recordComponents.length) {
builder.range(deconstructionList);
PsiRecordComponent[] missingRecordComponents =
Arrays.copyOfRange(recordComponents, patternComponents.length, recordComponents.length);
List<Pattern> missingPatterns =
ContainerUtil.map(missingRecordComponents, component -> Pattern.create(component, deconstructionList));
ModCommandAction fix = new AddMissingDeconstructionComponentsFix(deconstructionList, missingPatterns);
builder.registerFix(fix, null, null, null, null);
}
else {
PsiPattern[] deconstructionComponents = deconstructionList.getDeconstructionComponents();
int endOffset = deconstructionList.getTextLength();
int startOffset = deconstructionComponents[recordComponents.length].getStartOffsetInParent();
TextRange textRange = TextRange.create(startOffset, endOffset);
builder.range(deconstructionList, textRange);
PsiPattern[] elementsToDelete = Arrays.copyOfRange(patternComponents, recordComponents.length, patternComponents.length);
int diff = patternComponents.length - recordComponents.length;
String text = QuickFixBundle.message("remove.redundant.nested.patterns.fix.text", diff);
IntentionAction fix = QuickFixFactory.getInstance().createDeleteFix(elementsToDelete, text);
builder.registerFix(fix, null, text, null, null);
}
}
else {
builder.range(deconstructionList);
}
return builder;
}
/**
* Create light description for patterns
*/
@@ -10,7 +10,6 @@ import com.intellij.modcommand.ModCommandAction;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
@@ -62,7 +61,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
List<PsiElement> elementsToCheckDominance = new ArrayList<>();
List<PsiCaseLabelElement> elementsToCheckCompleteness = new ArrayList<>();
int switchBlockGroupCounter = 0;
boolean reported = false;
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
fillElementsToCheckFallThroughLegality(elementsToCheckFallThroughLegality, labelStatement, switchBlockGroupCounter);
@@ -78,10 +76,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
PsiCaseLabelElementList labelElementList = labelStatement.getCaseLabelElementList();
if (labelElementList == null) continue;
for (PsiCaseLabelElement labelElement : labelElementList.getElements()) {
if (checkLabelAndSelectorCompatibility(labelElement, errorSink)) {
reported = true;
continue;
}
fillElementsToCheckDuplicates(elementsToCheckDuplicates, labelElement);
fillElementsToCheckDominance(elementsToCheckDominance, labelElement);
elementsToCheckCompleteness.add(labelElement);
@@ -91,11 +85,9 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
if (checkDuplicates(elementsToCheckDuplicates, errorSink)) {
return;
}
if (reported) {
return;
}
Set<PsiElement> alreadyFallThroughElements = new HashSet<>();
reported = checkFallThroughFromPatternWithSeveralLabels(elementsToCheckFallThroughLegality, alreadyFallThroughElements, errorSink);
boolean reported =
checkFallThroughFromPatternWithSeveralLabels(elementsToCheckFallThroughLegality, alreadyFallThroughElements, errorSink);
reported |= checkFallThroughToPatternPrecedingCompleteNormally(elementsToCheckFallThroughLegality, alreadyFallThroughElements, errorSink);
if (reported) {
return;
@@ -110,145 +102,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
}
}
private boolean checkLabelAndSelectorCompatibility(@NotNull PsiCaseLabelElement label,
@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
if (label instanceof PsiDefaultCaseLabelElement) return false;
if (!(label instanceof PsiParenthesizedExpression) && isNullType(label)) {
if (mySelectorType instanceof PsiPrimitiveType && !isNullType(mySelector)) {
HighlightInfo.Builder error = createError(label, JavaErrorBundle.message("incompatible.switch.null.type", "null",
JavaHighlightUtil.formatType(mySelectorType)));
errorSink.accept(error);
return true;
}
return false;
}
if (label instanceof PsiPattern) {
PsiPattern elementToReport = JavaPsiPatternUtil.getTypedPattern(label);
if (elementToReport == null) return false;
PsiTypeElement typeElement = JavaPsiPatternUtil.getPatternTypeElement(elementToReport);
if (typeElement == null) return false;
PsiType patternType = typeElement.getType();
if (!(patternType instanceof PsiClassType) && !(patternType instanceof PsiArrayType) &&
!JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(myLevel)) {
String expectedTypes = JavaErrorBundle.message("switch.class.or.array.type.expected");
String message = JavaErrorBundle.message("unexpected.type", expectedTypes, JavaHighlightUtil.formatType(patternType));
HighlightInfo.Builder info = createError(elementToReport, message);
if (patternType instanceof PsiPrimitiveType) {
HighlightInfo.Builder infoFeature =
HighlightUtil.checkFeature(elementToReport, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS,
PsiUtil.getLanguageLevel(elementToReport), elementToReport.getContainingFile());
if (infoFeature != null) {
info = infoFeature;
}
}
PsiPrimitiveType primitiveType = ObjectUtils.tryCast(patternType, PsiPrimitiveType.class);
if (primitiveType != null) {
IntentionAction fix = getFixFactory().createReplacePrimitiveWithBoxedTypeAction(mySelectorType, typeElement);
if (fix != null) {
info.registerFix(fix, null, null, null, null);
}
}
errorSink.accept(info);
return true;
}
if ((!ContainerUtil.and(JavaPsiPatternUtil.deconstructSelectorType(mySelectorType), type -> TypeConversionUtil.areTypesConvertible(type, patternType)) ||
// 14.30.3 A type pattern that declares a pattern variable of a reference type U is
// applicable at another reference type T if T is checkcast convertible to U (JEP 440-441)
// There is no rule that says that a reference type applies to a primitive type
(mySelectorType instanceof PsiPrimitiveType &&
PsiUtil.isAvailable(JavaFeature.PATTERN_GUARDS_AND_RECORD_PATTERNS, label)) &&
//from JEP 455 it is allowed
!PsiUtil.isAvailable(JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS, label)) &&
//null type is applicable to any class type
!mySelectorType.equals(PsiTypes.nullType())) {
if (!IncompleteModelUtil.isIncompleteModel(label) ||
(!IncompleteModelUtil.isPotentiallyConvertible(mySelectorType, patternType, label))) {
HighlightInfo.Builder error =
HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, patternType, elementToReport.getTextRange());
if (mySelectorType instanceof PsiPrimitiveType) {
HighlightInfo.Builder infoFeature =
HighlightUtil.checkFeature(elementToReport, JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS,
PsiUtil.getLanguageLevel(elementToReport), elementToReport.getContainingFile());
if (infoFeature != null) {
error = infoFeature;
}
}
errorSink.accept(error);
return true;
}
}
HighlightInfo.Builder error = getUncheckedPatternConversionError(elementToReport);
if (error != null) {
errorSink.accept(error);
return true;
}
PsiDeconstructionPattern deconstructionPattern = JavaPsiPatternUtil.findDeconstructionPattern(elementToReport);
return createDeconstructionErrors(deconstructionPattern, errorSink);
}
else if (label instanceof PsiExpression expr) {
if (mySelectorType.equals(PsiTypes.nullType())) {
HighlightInfo.Builder info =
HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), expr.getTextRange());
errorSink.accept(info);
return true;
}
HighlightInfo.Builder info = HighlightUtil.checkAssignability(mySelectorType, expr.getType(), expr, expr);
if (info != null) {
errorSink.accept(info);
return true;
}
if (label instanceof PsiReferenceExpression ref) {
String enumConstName = evaluateEnumConstantName(ref);
if (enumConstName != null) {
HighlightInfo.Builder error = createQualifiedEnumConstantInfo(ref);
if (error != null) {
errorSink.accept(error);
return true;
}
return false;
}
}
Object constValue = evaluateConstant(expr);
if (constValue == null) {
HighlightInfo.Builder error = createError(expr, JavaErrorBundle.message("constant.expression.required"));
errorSink.accept(error);
return true;
}
JavaPsiSwitchUtil.SelectorKind kind = mySelectorKind;
if (isExtendedPrimitiveSelector()) {
if ((kind == JavaPsiSwitchUtil.SelectorKind.LONG && !(constValue instanceof Long)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.DOUBLE && !(constValue instanceof Double)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.FLOAT && !(constValue instanceof Float)) ||
(kind == JavaPsiSwitchUtil.SelectorKind.BOOLEAN && !(constValue instanceof Boolean))) {
PsiType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(mySelectorType);
if (unboxedType != null) {
HighlightInfo.Builder error =
HighlightUtil.createIncompatibleTypeHighlightInfo(unboxedType, expr.getType(), label.getTextRange());
errorSink.accept(error);
return true;
}
}
return false;
}
if (ConstantExpressionUtil.computeCastTo(constValue, mySelectorType) == null) {
HighlightInfo.Builder error =
HighlightUtil.createIncompatibleTypeHighlightInfo(mySelectorType, expr.getType(), label.getTextRange());
errorSink.accept(error);
return true;
}
if (kind == JavaPsiSwitchUtil.SelectorKind.INT || kind == JavaPsiSwitchUtil.SelectorKind.STRING) {
return false;
}
HighlightInfo.Builder infoIncompatibleTypes =
createError(expr, JavaErrorBundle.message("switch.pattern.expected", JavaHighlightUtil.formatType(mySelectorType)));
errorSink.accept(infoIncompatibleTypes);
return true;
}
HighlightInfo.Builder error = createError(label, JavaErrorBundle.message("switch.constant.expression.required"));
errorSink.accept(error);
return true;
}
@Override
void fillElementsToCheckDuplicates(@NotNull MultiMap<Object, PsiElement> elements, @NotNull PsiCaseLabelElement labelElement) {
if (labelElement instanceof PsiDefaultCaseLabelElement) {
@@ -280,13 +133,6 @@ public class PatternsInSwitchBlockHighlightingModel extends SwitchBlockHighlight
}
}
private boolean isExtendedPrimitiveSelector() {
return mySelectorKind == JavaPsiSwitchUtil.SelectorKind.BOOLEAN ||
mySelectorKind == JavaPsiSwitchUtil.SelectorKind.FLOAT ||
mySelectorKind == JavaPsiSwitchUtil.SelectorKind.DOUBLE ||
mySelectorKind == JavaPsiSwitchUtil.SelectorKind.LONG;
}
private static void fillElementsToCheckFallThroughLegality(@NotNull List<List<PsiSwitchLabelStatementBase>> elements,
@NotNull PsiSwitchLabelStatementBase labelStatement,
int switchBlockGroupCounter) {
@@ -91,7 +91,6 @@ public class SwitchBlockHighlightingModel {
MultiMap<Object, PsiElement> elementsToCheckDuplicates = new MultiMap<>();
boolean hasDefaultCase = false;
boolean reported = false;
for (PsiStatement st : body.getStatements()) {
if (!(st instanceof PsiSwitchLabelStatementBase labelStatement)) continue;
@@ -107,56 +106,14 @@ public class SwitchBlockHighlightingModel {
}
for (PsiCaseLabelElement labelElement : labelElementList.getElements()) {
if (labelElement instanceof PsiExpression expr) {
HighlightInfo.Builder info = HighlightUtil.checkAssignability(mySelectorType, expr.getType(), expr, expr);
if (info != null) {
errorSink.accept(info);
reported = true;
continue;
}
Object value = null;
if (expr instanceof PsiReferenceExpression ref) {
String enumConstName = evaluateEnumConstantName(ref);
if (enumConstName != null) {
value = enumConstName;
HighlightInfo.Builder info2 = createQualifiedEnumConstantInfo(ref);
if (info2 != null) {
errorSink.accept(info2);
reported = true;
continue;
}
}
}
if (value == null) {
value = ConstantExpressionUtil.computeCastTo(expr, mySelectorType);
}
if (value == null) {
errorSink.accept(createError(expr, JavaErrorBundle.message("constant.expression.required")));
reported = true;
continue;
}
fillElementsToCheckDuplicates(elementsToCheckDuplicates, expr);
}
else if (labelElement instanceof PsiDefaultCaseLabelElement defaultElement && labelElementList.getElementCount() == 1) {
// if default is not the only case in the label, insufficient language level will be reported
// see HighlightVisitorImpl#visitDefaultCaseLabelElement
HighlightInfo.Builder info = createError(defaultElement, JavaErrorBundle.message("default.label.must.not.contains.case.keyword"));
ModCommandAction fix = getFixFactory().createReplaceCaseDefaultWithDefaultFix(labelElementList);
info.registerFix(fix, null, null, null, null);
errorSink.accept(info);
reported = true;
}
else if (labelElement instanceof PsiPattern) {
// ignore patterns. If they appear here, insufficient language level will be reported
}
}
}
if (checkDuplicates(elementsToCheckDuplicates, errorSink)) {
return;
}
if (reported) {
return;
}
// todo replace with needToCheckCompleteness
if (myBlock instanceof PsiSwitchExpression && !hasDefaultCase) {
@@ -185,17 +142,6 @@ public class SwitchBlockHighlightingModel {
return null;
}
static @Nullable HighlightInfo.Builder createQualifiedEnumConstantInfo(@NotNull PsiReferenceExpression expr) {
if (PsiUtil.isAvailable(JavaFeature.ENUM_QUALIFIED_NAME_IN_SWITCH, expr)) return null;
PsiElement qualifier = expr.getQualifier();
if (qualifier == null) return null;
HighlightInfo.Builder result = createError(expr, JavaErrorBundle.message("qualified.enum.constant.in.switch"));
IntentionAction action = getFixFactory().createDeleteFix(qualifier, JavaErrorBundle.message(
"qualified.enum.constant.in.switch.remove.fix"));
result.registerFix(action, null, null, null, null);
return result;
}
private static QuickFixFactory getFixFactory() {
return QuickFixFactory.getInstance();
}
@@ -33,7 +33,7 @@ public class Incompatible {
case PrimitiveRecord(int x) when true -> {}
case IntegerRecord(Integer x) when true -> {}
case IntegerRecord(<error descr="Primitive types in patterns, instanceof and switch are not supported at language level '21'">int x</error>) when true -> {}
case <error descr="'Object' cannot be safely cast to 'T'">T(Integer x)</error> -> {}
case <error descr="'Object' cannot be safely cast to 'T'"><error descr="Deconstruction pattern can only be applied to a record, 'T' is not a record">T</error>(Integer x)</error> -> {}
}
switch (integer){
@@ -22,7 +22,7 @@ class Test {
enum E{A}
public void test4() {
switch (null) {
case <error descr="Incompatible types. Found: 'Test.E', required: 'null'">E.A</error> -> System.out.println("2"); //error
case E.<error descr="Incompatible types. Found: 'Test.E', required: 'null'">A</error> -> System.out.println("2"); //error
}
}
public void test5() {
@@ -241,8 +241,11 @@ class Main {
System.out.println("ok");
};
str = switch (d) {
case MONDAY, TUESDAY -> <error descr="Bad type in switch expression: void cannot be converted to java.lang.String">System.out.println("ok")</error>;
};
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
case MONDAY, TUESDAY -> System.out.println("ok");
case MONDAY, TUESDAY -> "ok";
};
str = switch (d) {
case MONDAY, TUESDAY, WEDNESDAY -> "ok";
@@ -246,8 +246,8 @@ class Main {
System.out.println("ok");
}
str = switch (<error descr="'switch' expression does not cover all possible input values">d</error>) {
case MONDAY, TUESDAY -> System.out.println("ok");
str = switch (d) {
case MONDAY, TUESDAY -> <error descr="Bad type in switch expression: void cannot be converted to java.lang.String">System.out.println("ok")</error>;
};
str = switch (d) {
case MONDAY, TUESDAY, WEDNESDAY -> "ok";
@@ -1,58 +1,6 @@
class IncompleteSwitch {
public void testStatement(char o) {
switch (o) {
case
<error descr="':' or '->' expected"><error descr="Expression, pattern, 'default' or 'null' expected">}</error></error>
switch (o) {
case '1'<EOLError descr="':' or '->' expected"></EOLError>
}
switch (o) {
case '1' when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
}
switch (o) {
case <error descr="Primitive types in patterns, instanceof and switch are not supported at language level '21'">char a</error> when a == '1'<EOLError descr="':' or '->' expected"></EOLError>
}
switch (o) {
case '2' -> System.out.println("1");
case
<error descr="':' or '->' expected"><error descr="Expression, pattern, 'default' or 'null' expected">}</error></error>
switch (o) {
case '2' -> System.out.println("1");
case '1'<EOLError descr="':' or '->' expected"></EOLError>
}
switch (o) {
case '2' -> System.out.println("1");
case '1' when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
}
switch (o) {
case '2' -> System.out.println("1");
case <error descr="Primitive types in patterns, instanceof and switch are not supported at language level '21'">char a</error> when a == '1'<EOLError descr="':' or '->' expected"></EOLError>
}
}
public void testExpression(char o) {
int i = switch (<error descr="'switch' expression does not cover all possible input values">o</error>) {
case '2':
yield 1;
case
<error descr="':' or '->' expected"><error descr="Expression, pattern, 'default' or 'null' expected">}</error></error>;
i = switch (<error descr="'switch' expression does not cover all possible input values">o</error>) {
case '2':
yield 2;
case '1'<EOLError descr="':' or '->' expected"></EOLError>
};
i = switch (<error descr="'switch' expression does not cover all possible input values">o</error>) {
case '2':
yield 2;
case '1' when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
};
i = switch (o) {
int i = switch (o) {
case '2':
yield 2;
case <error descr="Primitive types in patterns, instanceof and switch are not supported at language level '21'">char a</error> when a == '1'<EOLError descr="':' or '->' expected"></EOLError>
@@ -47,13 +47,13 @@ class C {
}
void defaultBranchAlwaysThrows(int n) {
String s = <error descr="Switch expression does not have any result expressions">switch</error> (n) {
String s = <error descr="'switch' expression does not have any result expressions">switch</error> (n) {
default: throw new RuntimeException();
};
}
void defaultRuleAlwaysThrows(int n) {
String s = <error descr="Switch expression does not have any result expressions">switch</error> (n) {
String s = <error descr="'switch' expression does not have any result expressions">switch</error> (n) {
default -> throw new RuntimeException();
};
}
@@ -10,9 +10,9 @@ class SwitchExpressions {
System.out.println(switch (<error descr="'switch' expression does not have any case clauses">new Random().nextInt()</error>) { });
System.out.println(switch (<error descr="'switch' expression does not cover all possible input values">new Random().nextInt()</error>) {
System.out.println(switch (new Random().nextInt()) {
case 0 -> throw new IllegalStateException("no args");
case 1: yield "lone";
<error descr="Different 'case' kinds used in 'switch'">case 1:</error> yield "lone";
});
System.out.println(
@@ -1,12 +1,12 @@
class Test {
void test() {
int i = <error descr="Switch expression does not have any result expressions">switch</error>(0) {
int i = <error descr="'switch' expression does not have any result expressions">switch</error>(0) {
default -> throw new NullPointerException();
};
}
void test2() {
int i = <error descr="Switch expression does not have any result expressions">switch</error>(0) {
int i = <error descr="'switch' expression does not have any result expressions">switch</error>(0) {
case 0 -> {while(true);}
case 1 -> {
throw new RuntimeException();
@@ -71,9 +71,9 @@ class ConditionalExpressionWithIdenticalBranches {
} : switch (e) {
default: yield 2; // different yield value expression
};
int yy = (i == 10) ? <error descr="Switch expression does not have any result expressions">switch</error> (e) {
int yy = (i == 10) ? <error descr="'switch' expression does not have any result expressions">switch</error> (e) {
default -> throw new InternalError();
}: <error descr="Switch expression does not have any result expressions">switch</error> (e) {
}: <error descr="'switch' expression does not have any result expressions">switch</error> (e) {
default -> throw new InternalError((String) null); // different default body
};
}
@@ -1,6 +1,6 @@
class Test {
int testIncomplete(Object obj) {
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
return <error descr="Switch expression should produce a result in all execution paths">switch</error>(obj) {
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
};
}
@@ -1,6 +1,6 @@
class Test {
int testIncomplete(Object obj) {
return switch(<error descr="'switch' expression does not cover all possible input values">obj</error>) {
return <error descr="Switch expression should produce a result in all execution paths">switch</error>(obj) {
case String s when<EOLError descr="Expression expected"></EOLError><EOLError descr="':' or '->' expected"></EOLError>
};
}