mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
committed by
intellij-monorepo-bot
parent
7bb33c7a68
commit
9e458c909c
+8
-1
@@ -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
|
||||
|
||||
+13
-9
@@ -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) {
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+9
-9
@@ -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 {
|
||||
|
||||
+179
-5
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -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 =
|
||||
|
||||
+25
@@ -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;
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+6
-8
@@ -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() {
|
||||
|
||||
-186
@@ -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
|
||||
*/
|
||||
|
||||
+2
-156
@@ -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) {
|
||||
|
||||
-54
@@ -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();
|
||||
}
|
||||
|
||||
+1
-1
@@ -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){
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
|
||||
+4
-1
@@ -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";
|
||||
|
||||
+2
-2
@@ -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
-53
@@ -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>
|
||||
|
||||
+2
-2
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+2
-2
@@ -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>
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user