[java-highlighting] checkSwitchSelectorType migrated

Error messages unified; do not list allowed switch types anymore
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 69675af87ac8866fb5ba1475094b93af07aa1ad5
This commit is contained in:
Tagir Valeev
2025-02-17 14:34:40 +01:00
committed by intellij-monorepo-bot
parent 16c58ccd5d
commit cb626d5749
16 changed files with 75 additions and 68 deletions

View File

@@ -316,6 +316,8 @@ switch.expression.incompatible.type=Bad type in switch expression: {0} cannot be
switch.expression.cannot.be.void=Target type for switch expression cannot be void
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}''
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'

View File

@@ -1789,4 +1789,23 @@ final class ExpressionChecker {
}
myVisitor.report(JavaErrorKinds.SWITCH_DIFFERENT_CASE_KINDS.create(alien));
}
void checkSwitchSelectorType(@NotNull PsiSwitchBlock block) {
PsiExpression selector = block.getExpression();
if (selector == null) return;
PsiType selectorType = selector.getType();
if (selectorType == null) return;
JavaPsiSwitchUtil.SelectorKind kind = JavaPsiSwitchUtil.getSwitchSelectorKind(selectorType);
JavaFeature requiredFeature = kind.getFeature();
if ((kind == JavaPsiSwitchUtil.SelectorKind.INVALID || requiredFeature != null && !myVisitor.isApplicable(requiredFeature)) &&
!PsiTreeUtil.hasErrorElements(block)) {
myVisitor.report(JavaErrorKinds.SWITCH_SELECTOR_TYPE_INVALID.create(selector, kind));
}
PsiClass member = PsiUtil.resolveClassInClassTypeOnly(selectorType);
if (member != null && !PsiUtil.isAccessible(member.getProject(), member, selector, null)) {
myVisitor.report(JavaErrorKinds.TYPE_INACCESSIBLE.create(selector, member));
}
}
}

View File

@@ -625,10 +625,10 @@ final class JavaErrorVisitor extends JavaElementVisitor {
@Override
public void visitSwitchExpression(@NotNull PsiSwitchExpression expression) {
super.visitSwitchExpression(expression);
if (!hasErrorResults()) checkFeature(expression, JavaFeature.SWITCH_EXPRESSION);
checkSwitchBlock(expression);
if (!hasErrorResults()) checkFeature(expression.getFirstChild(), JavaFeature.SWITCH_EXPRESSION);
if (!hasErrorResults()) myExpressionChecker.checkSwitchExpressionReturnTypeCompatible(expression);
if (!hasErrorResults()) myExpressionChecker.checkSwitchExpressionHasResult(expression);
checkSwitchBlock(expression);
}
@Override
@@ -639,6 +639,7 @@ final class JavaErrorVisitor extends JavaElementVisitor {
private void checkSwitchBlock(@NotNull PsiSwitchBlock block) {
if (!hasErrorResults()) myExpressionChecker.checkSwitchBlockStatements(block);
if (!hasErrorResults()) myExpressionChecker.checkSwitchSelectorType(block);
}
@Override

View File

@@ -925,6 +925,12 @@ public final class JavaErrorKinds {
.withRawDescription((expr, context) -> message("switch.expression.incompatible.type", formatType(context.rType()), formatType(context.lType())));
public static final Simple<PsiElement> SWITCH_LABEL_EXPECTED = error(PsiElement.class, "switch.label.expected");
public static final Simple<PsiElement> SWITCH_DIFFERENT_CASE_KINDS = error("switch.different.case.kinds");
public static final Parameterized<PsiExpression, JavaPsiSwitchUtil.SelectorKind> SWITCH_SELECTOR_TYPE_INVALID =
parameterized(PsiExpression.class, JavaPsiSwitchUtil.SelectorKind.class, "switch.selector.type.invalid")
.withRawDescription((expr, kind) -> kind.getFeature() == null ?
message("switch.selector.type.invalid", formatType(expr.getType())) :
message("switch.selector.type.invalid.level", formatType(expr.getType()),
PsiUtil.getLanguageLevel(expr).getShortText()));
public static final Simple<PsiReferenceExpression> EXPRESSION_EXPECTED = error("expression.expected");
public static final Parameterized<PsiReferenceExpression, PsiSuperExpression> EXPRESSION_SUPER_UNQUALIFIED_DEFAULT_METHOD =

View File

@@ -899,6 +899,21 @@ public final class HighlightFixUtil {
factory.createShowModulePropertiesFix(element));
}
static void registerFixesOnInvalidSelector(@NotNull PsiExpression selector,
@NotNull Consumer<? super @Nullable CommonIntentionAction> sink) {
QuickFixFactory factory = QuickFixFactory.getInstance();
if (selector.getParent() instanceof PsiSwitchStatement switchStatement) {
sink.accept(factory.createConvertSwitchToIfIntention(switchStatement));
}
PsiType selectorType = selector.getType();
if (PsiTypes.longType().equals(selectorType) ||
PsiTypes.floatType().equals(selectorType) ||
PsiTypes.doubleType().equals(selectorType)) {
sink.accept(factory.createAddTypeCastFix(PsiTypes.intType(), selector));
sink.accept(factory.createWrapWithAdapterFix(PsiTypes.intType(), selector));
}
}
private static final class ReturnModel {
final PsiReturnStatement myStatement;
final PsiType myType;

View File

@@ -327,7 +327,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
private void checkSwitchBlock(@NotNull PsiSwitchBlock switchBlock) {
SwitchBlockHighlightingModel model = SwitchBlockHighlightingModel.createInstance(myLanguageLevel, switchBlock, myFile);
if (model == null) return;
if (!hasErrorResults()) model.checkSwitchSelectorType(myErrorSink);
if (!hasErrorResults()) model.checkSwitchLabelValues(myErrorSink);
}

View File

@@ -228,6 +228,13 @@ final class JavaErrorFixProvider {
PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(error.psi(), PsiSwitchLabeledRuleStatement.class);
return previousRule == null ? null : myFactory.createWrapSwitchRuleStatementsIntoBlockFix(previousRule);
});
fixes(SWITCH_SELECTOR_TYPE_INVALID, (error, sink) -> {
HighlightFixUtil.registerFixesOnInvalidSelector(error.psi(), sink);
JavaFeature feature = error.context().getFeature();
if (feature != null) {
HighlightFixUtil.getIncreaseLanguageLevelFixes(error.psi(), feature).forEach(sink);
}
});
}
private void createMethodFixes() {

View File

@@ -12,7 +12,10 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.psi.util.ConstantExpressionUtil;
import com.intellij.psi.util.JavaPsiSwitchUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
@@ -80,48 +83,6 @@ public class SwitchBlockHighlightingModel {
return found.get();
}
void checkSwitchSelectorType(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
JavaPsiSwitchUtil.SelectorKind kind = getSwitchSelectorKind();
if (kind == JavaPsiSwitchUtil.SelectorKind.INT) return;
JavaFeature requiredFeature = kind.getFeature();
if (kind == JavaPsiSwitchUtil.SelectorKind.INVALID || requiredFeature != null && !requiredFeature.isSufficient(myLevel)) {
String message;
if (JavaFeature.PATTERNS_IN_SWITCH.isSufficient(myLevel)) {
message = JavaErrorBundle.message("switch.invalid.selector.types",
JavaHighlightUtil.formatType(mySelectorType));
}
else {
boolean is7 = JavaFeature.STRING_SWITCH.isSufficient(myLevel);
String expected = JavaErrorBundle.message(is7 ? "valid.switch.1_7.selector.types" : "valid.switch.selector.types");
message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(mySelectorType));
}
HighlightInfo.Builder info = createError(mySelector, message);
registerFixesOnInvalidSelector(info);
if (requiredFeature != null) {
HighlightUtil.registerIncreaseLanguageLevelFixes(mySelector, requiredFeature, info);
}
errorSink.accept(info);
}
checkIfAccessibleType(errorSink);
}
private void registerFixesOnInvalidSelector(HighlightInfo.Builder builder) {
if (myBlock instanceof PsiSwitchStatement switchStatement) {
IntentionAction action = getFixFactory().createConvertSwitchToIfIntention(switchStatement);
builder.registerFix(action, null, null, null, null);
}
if (PsiTypes.longType().equals(mySelectorType) ||
PsiTypes.floatType().equals(mySelectorType) ||
PsiTypes.doubleType().equals(mySelectorType)) {
IntentionAction addTypeCastFix = getFixFactory().createAddTypeCastFix(PsiTypes.intType(), mySelector);
builder.registerFix(addTypeCastFix, null, null, null, null);
IntentionAction wrapWithAdapterFix = getFixFactory().createWrapWithAdapterFix(PsiTypes.intType(), mySelector);
builder.registerFix(wrapWithAdapterFix, null, null, null, null);
}
}
void checkSwitchLabelValues(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
PsiCodeBlock body = myBlock.getBody();
if (body == null) return;
@@ -231,14 +192,6 @@ public class SwitchBlockHighlightingModel {
return QuickFixFactory.getInstance();
}
void checkIfAccessibleType(@NotNull Consumer<? super HighlightInfo.Builder> errorSink) {
PsiClass member = PsiUtil.resolveClassInClassTypeOnly(mySelectorType);
if (member != null && !PsiUtil.isAccessible(member.getProject(), member, mySelector, null)) {
String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME);
errorSink.accept(createError(mySelector, JavaErrorBundle.message("inaccessible.type", className)));
}
}
void fillElementsToCheckDuplicates(@NotNull MultiMap<Object, PsiElement> elements, @NotNull PsiCaseLabelElement labelElement) {
PsiExpression expr = ObjectUtils.tryCast(labelElement, PsiExpression.class);
if (expr == null) return;

View File

@@ -54,8 +54,6 @@ unexpected.type=Unexpected type. Found: ''{1}'', required: ''{0}''
incompatible.types.reason.ambiguous.method.reference=<br/>reason: method reference is ambiguous: both ''{0}'' and ''{1}'' match
incompatible.switch.null.type=''{0}'' cannot be converted to ''{1}''
inaccessible.type=''{0}'' is inaccessible from here
valid.switch.selector.types=byte, char, short or int
valid.switch.1_7.selector.types=char, byte, short, int, Character, Byte, Short, Integer, String, or an enum
switch.illegal.fall.through.to=Illegal fall-through to a pattern
invalid.case.label.combination.constants.and.patterns=Invalid case label combination: a case label must consist of either a list of case constants or a single case pattern
invalid.case.label.combination.constants.and.patterns.unnamed=Invalid case label combination: a case label must consist of either a list of case constants or a list of case patterns

View File

@@ -89,8 +89,11 @@ public final class JavaPsiSwitchUtil {
/**
* Returns the selector kind based on the type.
* <p>
* It's not checked whether this particular kind is supported at a given location
* (the method does not have location information anyway).
* The result may depend on type language level for boxed primitive types.
* E.g., if selector type is {@link Double} then {@link SelectorKind#DOUBLE} will be returned
* if primitives in patterns are supported, but {@link SelectorKind#CLASS_OR_ARRAY} will be returned otherwise.
* <p>
* It's not checked whether this particular kind is supported at a given location.
* It's up to the caller to check this using the {@link SelectorKind#getFeature()} method.
*
* @param selectorType type of switch selector expression
@@ -100,7 +103,9 @@ public final class JavaPsiSwitchUtil {
if (TypeConversionUtil.getTypeRank(selectorType) <= TypeConversionUtil.INT_RANK) {
return SelectorKind.INT;
}
PsiType unboxedType = PsiPrimitiveType.getOptionallyUnboxedType(selectorType);
PsiType unboxedType = selectorType instanceof PsiClassType &&
JavaFeature.PRIMITIVE_TYPES_IN_PATTERNS.isSufficient(((PsiClassType)selectorType).getLanguageLevel()) ?
PsiPrimitiveType.getOptionallyUnboxedType(selectorType) : selectorType;
if (unboxedType != null) {
if (unboxedType.equals(PsiTypes.longType())) {
return SelectorKind.LONG;

View File

@@ -1,7 +1,9 @@
class c {
void f() {
Integer x = new Integer(0);
//---- switch --------------------------------------------------------
switch (<error descr="Incompatible types. Found: 'java.lang.String', required: 'byte, char, short or int'">"s"</error>)
switch (<error descr="Selector type of 'java.lang.Integer' is not supported at language level '1.4'">x</error>) {default:}
switch (<error descr="Selector type of 'java.lang.String' is not supported at language level '1.4'">"s"</error>)
{default:}
byte bt = 0;
switch (bt) {

View File

@@ -20,7 +20,7 @@ class UnsupportedFeatures {
boolean b1 = Boolean.<error descr="Incompatible types. Found: 'java.lang.Boolean', required: 'boolean'">TRUE</error>;
java.lang.annotation.ElementType t = null;
switch (<error descr="Incompatible types. Found: 'java.lang.annotation.ElementType', required: 'byte, char, short or int'">t</error>) { }
switch (<error descr="Selector type of 'java.lang.annotation.ElementType' is not supported at language level '1.4'">t</error>) { }
String raw = <error descr="Text block literals are not supported at language level '1.4'">"""hi there"""</error>;

View File

@@ -21,7 +21,7 @@ class UnsupportedFeatures {
I i1 = <error descr="Method references are not supported at language level '6'">UnsupportedFeatures::m</error>;
I i2 = <error descr="Lambda expressions are not supported at language level '6'">() -> { }</error>;
switch (<error descr="Incompatible types. Found: 'java.lang.String', required: 'byte, char, short or int'">list.get(0)</error>) {
switch (<error descr="Selector type of 'java.lang.String' is not supported at language level '6'">list.get(0)</error>) {
case "foo": break;
}

View File

@@ -43,7 +43,7 @@ public class SwitchWithPrimitivesNotAllowed {
private static void testPrimitives() {
boolean b = true;
switch (<error descr="Selector type of 'boolean' is not supported">b</error>) {//error
switch (<error descr="Selector type of 'boolean' is not supported at language level '22'">b</error>) {//error
default -> System.out.println("1");
}
int i = 1;
@@ -63,15 +63,15 @@ public class SwitchWithPrimitivesNotAllowed {
default -> System.out.println("1");
}
long l = 1L;
switch (<error descr="Selector type of 'long' is not supported">l</error>) { //error
switch (<error descr="Selector type of 'long' is not supported at language level '22'">l</error>) { //error
default -> System.out.println("1");
}
double d = 1.0;
switch (<error descr="Selector type of 'double' is not supported">d</error>) {//error
switch (<error descr="Selector type of 'double' is not supported at language level '22'">d</error>) {//error
default -> System.out.println("1");
}
float f = 1.0F;
switch (<error descr="Selector type of 'float' is not supported">f</error>) {//error
switch (<error descr="Selector type of 'float' is not supported at language level '22'">f</error>) {//error
default -> System.out.println("1");
}
}

View File

@@ -84,7 +84,7 @@ class EnhancedSwitchStatements {
noop(); break;
}
switch (<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">new Object()</error>) { }
switch (<error descr="Selector type of 'java.lang.Object' is not supported at language level '15'">new Object()</error>) { }
}
private static void noop() { }

View File

@@ -16,7 +16,7 @@ class SwitchExpressions {
});
System.out.println(
switch (<error descr="Incompatible types. Found: 'java.lang.Object', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'">new Object()</error>) {
switch (<error descr="Selector type of 'java.lang.Object' is not supported at language level '15'">new Object()</error>) {
default -> "whatever";
}
);