[java] value breaks: basic highlighting (IDEA-196643)

This commit is contained in:
Roman Shevchenko
2018-11-20 18:57:22 +01:00
parent ce161e00f3
commit d50cffbf0f
10 changed files with 164 additions and 104 deletions
@@ -28,6 +28,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
@@ -509,6 +510,15 @@ public class HighlightUtil extends HighlightUtilBase {
return highlightInfo;
}
@Nullable
static HighlightInfo checkReturnOutsideOfSwitchExpr(@NotNull PsiStatement statement) {
if (PsiImplUtil.findEnclosingSwitchOrLoop(statement) instanceof PsiSwitchExpression) {
String message = JavaErrorMessages.message("return.outside.switch.expr");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
return null;
}
@Nullable
static HighlightInfo checkReturnStatementType(@NotNull PsiReturnStatement statement) {
@@ -760,37 +770,66 @@ public class HighlightUtil extends HighlightUtilBase {
}
@Nullable
static HighlightInfo checkBreakOutsideLoop(@NotNull PsiBreakStatement statement) {
if (statement.getLabelIdentifier() == null) {
if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) {
String description = JavaErrorMessages.message("break.outside.switch.or.loop");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create();
}
}
else {
// todo labeled
static HighlightInfo checkBreakOutsideSwitchOrLoop(@NotNull PsiBreakStatement statement) {
PsiElement enclosing = PsiImplUtil.findEnclosingSwitchOrLoop(statement);
if (enclosing == null) {
String message = JavaErrorMessages.message("break.outside.switch.or.loop");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
return null;
}
@Nullable
static HighlightInfo checkValueBreakExpression(@NotNull PsiBreakStatement statement, @Nullable PsiExpression expression) {
PsiElement enclosing = PsiImplUtil.findEnclosingSwitchOrLoop(statement);
if (enclosing instanceof PsiSwitchExpression) {
if (expression == null) {
String message = JavaErrorMessages.message("value.break.missing");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
if (PsiTreeUtil.isAncestor(statement.findExitedElement(), enclosing, true)) {
String message = JavaErrorMessages.message("break.outside.switch.expr");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
}
else if (expression != null && (!PsiImplUtil.isPlainReference(expression) || ((PsiReferenceExpression)expression).resolve() instanceof PsiVariable)) {
String message = JavaErrorMessages.message("value.break.unexpected");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
return null;
}
@Nullable
static HighlightInfo checkContinueOutsideLoop(@NotNull PsiContinueStatement statement) {
if (statement.getLabelIdentifier() == null) {
if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) {
String description = JavaErrorMessages.message("continue.outside.loop");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create();
}
if (PsiImplUtil.findEnclosingLoop(statement) == null) {
String message = JavaErrorMessages.message("continue.outside.loop");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
else {
PsiStatement exitedStatement = statement.findContinuedStatement();
if (exitedStatement == null) return null;
if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) &&
!(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof PsiForeachStatement)) {
String description = JavaErrorMessages.message("not.loop.label", statement.getLabelIdentifier().getText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create();
return null;
}
@Nullable
static HighlightInfo checkContinueTarget(@NotNull PsiContinueStatement statement, @NotNull PsiIdentifier label, @NotNull LanguageLevel level) {
PsiStatement continuedStatement = statement.findContinuedStatement();
if (continuedStatement == null) {
String message = JavaErrorMessages.message("unresolved.label", label.getText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(label).descriptionAndTooltip(message).create();
}
if (!(continuedStatement instanceof PsiLoopStatement)) {
String message = JavaErrorMessages.message("not.loop.label", label.getText());
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
if (level.isAtLeast(LanguageLevel.JDK_12_PREVIEW)) {
PsiElement enclosing = PsiImplUtil.findEnclosingSwitchOrLoop(statement);
if (enclosing instanceof PsiSwitchExpression && PsiTreeUtil.isAncestor(continuedStatement, enclosing, true)) {
String message = JavaErrorMessages.message("continue.outside.switch.expr");
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(message).create();
}
}
return null;
}
@@ -1812,8 +1851,7 @@ public class HighlightUtil extends HighlightUtilBase {
if (resolved == null || resolved instanceof PsiVariable) return null;
PsiElement parent = expression.getParent();
// String.class or String() are both correct
if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) return null;
if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression || parent instanceof PsiBreakStatement) return null;
String description = JavaErrorMessages.message("expression.expected");
HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create();
@@ -2705,20 +2743,6 @@ public class HighlightUtil extends HighlightUtilBase {
return checkMustBeThrowable(type, context, false);
}
@Nullable
static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) {
if (labelIdentifier == null) return null;
String label = labelIdentifier.getText();
if (label == null) return null;
if (exitedStatement == null) {
String message = JavaErrorMessages.message("unresolved.label", label);
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelIdentifier).descriptionAndTooltip(message).create();
}
return null;
}
@Nullable
static HighlightInfo checkReference(@NotNull PsiJavaCodeReferenceElement ref,
@NotNull JavaResolveResult result,
@@ -2761,6 +2785,9 @@ public class HighlightUtil extends HighlightUtilBase {
String t2 = format(ObjectUtils.notNull(results[1].getElement()));
description = JavaErrorMessages.message("ambiguous.reference", refName.getText(), t1, t2);
}
else if (refParent instanceof PsiBreakStatement && !(PsiImplUtil.findEnclosingSwitchOrLoop(refParent) instanceof PsiSwitchExpression)) {
description = JavaErrorMessages.message("unresolved.label", refName.getText());
}
else {
description = JavaErrorMessages.message("cannot.resolve.symbol", refName.getText());
}
@@ -2838,6 +2865,7 @@ public class HighlightUtil extends HighlightUtilBase {
if (element instanceof PsiClass) return formatClass((PsiClass)element);
if (element instanceof PsiMethod) return JavaHighlightUtil.formatMethod((PsiMethod)element);
if (element instanceof PsiField) return formatField((PsiField)element);
if (element instanceof PsiLabeledStatement) return ((PsiLabeledStatement)element).getName() + ':';
return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE);
}
@@ -24,6 +24,7 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.ControlFlowUtil;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.javadoc.PsiDocMethodOrFieldRef;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
@@ -398,8 +399,13 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitBreakStatement(PsiBreakStatement statement) {
super.visitBreakStatement(statement);
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findExitedStatement()));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkBreakOutsideLoop(statement));
PsiExpression expression = statement.getExpression();
if (!myHolder.hasErrorResults() && expression == null) {
myHolder.add(HighlightUtil.checkBreakOutsideSwitchOrLoop(statement));
}
if (!myHolder.hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_12_PREVIEW)) {
myHolder.add(HighlightUtil.checkValueBreakExpression(statement, expression));
}
}
@Override
@@ -443,9 +449,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
public void visitContinueStatement(PsiContinueStatement statement) {
super.visitContinueStatement(statement);
if (!myHolder.hasErrorResults()) {
myHolder.add(HighlightUtil.checkLabelDefined(statement.getLabelIdentifier(), statement.findContinuedStatement()));
PsiIdentifier label = statement.getLabelIdentifier();
myHolder.add(label == null ? HighlightUtil.checkContinueOutsideLoop(statement)
: HighlightUtil.checkContinueTarget(statement, label, myLanguageLevel));
}
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkContinueOutsideLoop(statement));
}
@Override
@@ -519,21 +526,21 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitExpression(PsiExpression expression) {
ProgressManager.checkCanceled(); // visitLiteralExpression is invoked very often in array initializers
super.visitExpression(expression);
PsiType type = expression.getType();
if (myHolder.add(HighlightUtil.checkMustBeBoolean(expression, type))) return;
if (expression instanceof PsiArrayAccessExpression) {
myHolder.add(HighlightUtil.checkValidArrayAccessExpression((PsiArrayAccessExpression)expression));
}
PsiElement parent = expression.getParent();
if (parent instanceof PsiNewExpression
&& ((PsiNewExpression)parent).getQualifier() != expression
&& ((PsiNewExpression)parent).getArrayInitializer() != expression) {
// like in 'new String["s"]'
myHolder.add(HighlightUtil.checkAssignability(PsiType.INT, expression.getType(), expression, expression));
PsiType type = expression.getType();
if (!myHolder.hasErrorResults() && parent instanceof PsiBreakStatement && !PsiImplUtil.isPlainReference(expression)) {
myHolder.add(checkFeature(expression, Feature.SWITCH_EXPRESSION));
}
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkMustBeBoolean(expression, type));
if (!myHolder.hasErrorResults() && expression instanceof PsiArrayAccessExpression) {
myHolder.add(HighlightUtil.checkValidArrayAccessExpression((PsiArrayAccessExpression)expression));
}
if (!myHolder.hasErrorResults() && parent instanceof PsiNewExpression &&
((PsiNewExpression)parent).getQualifier() != expression && ((PsiNewExpression)parent).getArrayInitializer() != expression) {
myHolder.add(HighlightUtil.checkAssignability(PsiType.INT, expression.getType(), expression, expression)); // like in 'new String["s"]'
}
if (!myHolder.hasErrorResults()) myHolder.add(HighlightControlFlowUtil.checkCannotWriteToFinal(expression,myFile));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkVariableExpected(expression));
@@ -542,15 +549,10 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkAssertOperatorTypes(expression, type));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkSynchronizedExpressionType(expression, type, myFile));
if (!myHolder.hasErrorResults()) myHolder.add(HighlightUtil.checkConditionalExpressionBranchTypesMatch(expression, type));
if (!myHolder.hasErrorResults()
&& parent instanceof PsiThrowStatement
&& ((PsiThrowStatement)parent).getException() == expression && type != null) {
if (!myHolder.hasErrorResults() && parent instanceof PsiThrowStatement && ((PsiThrowStatement)parent).getException() == expression && type != null) {
myHolder.add(HighlightUtil.checkMustBeThrowable(type, expression, true));
}
if (!myHolder.hasErrorResults()) {
myHolder.add(AnnotationsHighlightUtil.checkConstantExpression(expression));
}
if (!myHolder.hasErrorResults()) myHolder.add(AnnotationsHighlightUtil.checkConstantExpression(expression));
if (!myHolder.hasErrorResults() && parent instanceof PsiForeachStatement && ((PsiForeachStatement)parent).getIteratedValue() == expression) {
myHolder.add(GenericsHighlightUtil.checkForeachExpressionTypeIsIterable(expression));
}
@@ -1558,10 +1560,16 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
try {
myHolder.add(HighlightUtil.checkReturnStatementType(statement));
super.visitStatement(statement);
if (!myHolder.hasErrorResults() && myLanguageLevel.isAtLeast(LanguageLevel.JDK_12_PREVIEW)) {
myHolder.add(HighlightUtil.checkReturnOutsideOfSwitchExpr(statement));
}
if (!myHolder.hasErrorResults()) {
try {
myHolder.add(HighlightUtil.checkReturnStatementType(statement));
}
catch (IndexNotReadyException ignore) { }
}
catch (IndexNotReadyException ignore) { }
}
@Override
@@ -1,21 +0,0 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;
import com.intellij.psi.*;
/**
* @author max
*/
public class EnclosingLoopMatcherExpression implements PsiMatcherExpression {
public static final PsiMatcherExpression INSTANCE = new EnclosingLoopMatcherExpression();
@Override
public Boolean match(PsiElement element) {
if (element instanceof PsiForStatement) return Boolean.TRUE;
if (element instanceof PsiForeachStatement) return Boolean.TRUE;
if (element instanceof PsiWhileStatement) return Boolean.TRUE;
if (element instanceof PsiDoWhileStatement) return Boolean.TRUE;
if (element instanceof PsiMethod || element instanceof PsiClassInitializer || element instanceof PsiLambdaExpression) return null;
return Boolean.FALSE;
}
}
@@ -1,17 +0,0 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;
import com.intellij.psi.*;
/**
* @author max
*/
public class EnclosingLoopOrSwitchMatcherExpression extends EnclosingLoopMatcherExpression {
public static final PsiMatcherExpression INSTANCE = new EnclosingLoopOrSwitchMatcherExpression();
@Override
public Boolean match(PsiElement element) {
if (element instanceof PsiSwitchBlock) return Boolean.TRUE;
return super.match(element);
}
}
@@ -223,6 +223,7 @@ unary.operator.not.applicable=Operator ''{0}'' cannot be applied to ''{1}''
return.outside.method=Return outside method
return.from.void.method=Cannot return a value from a method with void result type
missing.return.value=Missing return value
return.outside.switch.expr=Return outside of enclosing switch expression
#{0} - exceptions list (comma separated), {1} - exceptions count in the list, {2} - exception source
unhandled.exceptions=Unhandled {1, choice, 0#exception|2#exceptions}: {0}
@@ -230,7 +231,11 @@ unhandled.close.exceptions=Unhandled {1, choice, 0#exception|2#exceptions} from
variable.already.defined=Variable ''{0}'' is already defined in the scope
break.outside.switch.or.loop=Break outside switch or loop
value.break.unexpected=Value break outside switch expression
value.break.missing=Missing break value
break.outside.switch.expr=Break outside of enclosing switch expression
continue.outside.loop=Continue outside of loop
continue.outside.switch.expr=Continue outside of enclosing switch expression
not.loop.label=Not a loop label: ''{0}''
incompatible.modifiers=Illegal combination of modifiers: ''{0}'' and ''{1}''
modifier.not.allowed=Modifier ''{0}'' not allowed here
@@ -101,8 +101,8 @@ class C {
int n;
return 2;
n = <error descr="Unreachable statement">switch</error>(s) {
case "a": n= 1;break;
default: n= 0;
case "a": n = 1; break 1;
default: n = 0; break 0;
};
}
@@ -12,7 +12,7 @@ class SwitchExpressions {
System.out.println(switch (new Random().nextInt()) {
case 0 -> throw new IllegalStateException("no args");
<error descr="Different case kinds used in the switch">case 1:</error> break;
<error descr="Different case kinds used in the switch">case 1:</error> break "lone";
});
System.out.println(
@@ -44,5 +44,18 @@ class SwitchExpressions {
case E1 -> 1;
case E2 -> 2;
});
lab: while (true) {
switch (new Random().nextInt()) {
case -1: return;
case -2: continue lab;
default: break lab;
}
System.out.println(switch (new Random().nextInt()) {
case -1: <error descr="Return outside of enclosing switch expression">return;</error>
case -2: <error descr="Continue outside of enclosing switch expression">continue lab;</error>
default: <error descr="Break outside of enclosing switch expression">break lab;</error>
});
}
}
}
@@ -0,0 +1,39 @@
class ValueBreaks {
static final int ref = -1;
void m() {
l1: break l1;
<error descr="Break outside switch or loop">break;</error>
break <error descr="Undefined label: 'wtf'">wtf</error>;
<error descr="Value break outside switch expression">break 42;</error>
switch (0) {
case 0: <error descr="Value break outside switch expression">break 42;</error>
case 1: break <error descr="Undefined label: 'ref'">ref</error>;
case 2: break <error descr="Undefined label: 'wtf'">wtf</error>;
case 3: ref: break ref;
};
sink(switch (0) {
case 0 -> { while (true) <error descr="Value break outside switch expression">break 42;</error> }
case 1 -> { while (true) break <error descr="Undefined label: 'ref'">ref</error>; }
case 2 -> { while (true) break <error descr="Undefined label: 'wtf'">wtf</error>; }
case 3 -> { break ref; }
case 4 -> { break <error descr="Cannot resolve symbol 'wtf'">wtf</error>; }
default -> throw new RuntimeException();
});
ref: sink(switch (0) {
default: break <error descr="Reference to 'ref' is ambiguous, both 'ref:' and 'ValueBreaks.ref' match">ref</error>;
});
while (true) {
sink(switch (0) {
default: <error descr="Missing break value">break;</error>
});
}
}
private static void sink(Object o) { }
}
@@ -35,6 +35,10 @@ class UnsupportedFeatures {
System.out.println(<error descr="'switch' expressions are not supported at language level '6'">switch (list.size()) {
default -> "whoa!";
}</error>);
switch (list.size()) {
case 0: break <error descr="'switch' expressions are not supported at language level '6'">null</error>;
case 1: break <error descr="Undefined label: 'boo'">boo</error>;
}
}
void f(<error descr="Receiver parameters are not supported at language level '6'">Object this</error>) { }
@@ -10,6 +10,7 @@ class LightJava12HighlightingTest : LightCodeInsightFixtureTestCase() {
fun testEnhancedSwitchStatements() = doTest()
fun testSwitchExpressions() = doTest()
fun testValueBreaks() = doTest()
fun testSwitchNumericPromotion() = doTest()
fun testSimpleInferenceCases() = doTest()
fun testEnhancedSwitchDefinitelyAssigned() = doTest()