Support of enhanced Java 12 switch statements in DFA (IDEA-202132, no expressions yet)

This commit is contained in:
Tagir Valeev
2018-11-16 12:06:16 +07:00
parent 0e852a203d
commit 6bc07438c6
20 changed files with 285 additions and 58 deletions
@@ -401,9 +401,11 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override public void visitBreakStatement(PsiBreakStatement statement) {
startElement(statement);
jumpOut(statement.findExitedStatement());
finishElement(statement);
}
PsiStatement exitedStatement = statement.findExitedStatement();
private void jumpOut(PsiElement exitedStatement) {
if (exitedStatement != null && PsiTreeUtil.isAncestor(myCodeFragment, exitedStatement, false)) {
controlTransfer(new InstructionTransfer(getEndOffset(exitedStatement), getVariablesInside(exitedStatement)),
getTrapsInsideElement(exitedStatement));
@@ -411,8 +413,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
// Jumping out of analyzed code fragment
controlTransfer(ReturnTransfer.INSTANCE, getTrapsInsideElement(myCodeFragment));
}
finishElement(statement);
}
private void controlTransfer(@NotNull TransferTarget target, FList<Trap> traps) {
@@ -844,6 +844,19 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(statement);
}
@Override
public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) {
startElement(statement);
PsiStatement body = statement.getBody();
if (body != null) {
body.accept(this);
if (!(body instanceof PsiThrowStatement)) {
jumpOut(statement.getEnclosingSwitchBlock());
}
}
finishElement(statement);
}
@Override public void visitSwitchStatement(PsiSwitchStatement switchStmt) {
startElement(switchStmt);
PsiExpression caseExpression = switchStmt.getExpression();
@@ -889,34 +902,38 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (body != null) {
PsiStatement[] statements = body.getStatements();
PsiSwitchLabelStatement defaultLabel = null;
ControlFlowOffset offset = null;
PsiSwitchLabelStatementBase defaultLabel = null;
for (PsiStatement statement : statements) {
if (statement instanceof PsiSwitchLabelStatement) {
PsiSwitchLabelStatement psiLabelStatement = (PsiSwitchLabelStatement)statement;
if (statement instanceof PsiSwitchLabelStatementBase) {
PsiSwitchLabelStatementBase psiLabelStatement = (PsiSwitchLabelStatementBase)statement;
if (psiLabelStatement.isDefaultCase()) {
defaultLabel = psiLabelStatement;
}
else {
try {
offset = getStartOffset(statement);
PsiExpression caseValue = psiLabelStatement.getCaseValue();
PsiExpressionList values = psiLabelStatement.getCaseValues();
if (values != null) {
for (PsiExpression caseValue : values.getExpressions()) {
if (enumValues != null && caseValue instanceof PsiReferenceExpression) {
//noinspection SuspiciousMethodCalls
enumValues.remove(((PsiReferenceExpression)caseValue).resolve());
}
if (enumValues != null && caseValue instanceof PsiReferenceExpression) {
//noinspection SuspiciousMethodCalls
enumValues.remove(((PsiReferenceExpression)caseValue).resolve());
}
if (caseValue != null && expressionValue != null) {
addInstruction(new PushInstruction(expressionValue, null));
caseValue.accept(this);
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, PsiType.BOOLEAN));
}
else {
pushUnknown();
}
if (caseValue != null && expressionValue != null) {
addInstruction(new PushInstruction(expressionValue, null));
caseValue.accept(this);
addInstruction(new BinopInstruction(JavaTokenType.EQEQ, null, PsiType.BOOLEAN));
}
else {
pushUnknown();
}
addInstruction(new ConditionalGotoInstruction(offset, false, statement));
addInstruction(new ConditionalGotoInstruction(offset, false, caseValue));
}
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
@@ -305,25 +305,30 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
}
private void reportUnreachableSwitchBranches(Set<Instruction> trueSet, Set<Instruction> falseSet, ProblemsHolder holder) {
Set<PsiSwitchStatement> coveredSwitches = new HashSet<>();
Set<PsiSwitchLabelStatement> trueLabels = StreamEx.of(trueSet).select(BranchingInstruction.class)
.map(BranchingInstruction::getPsiAnchor).select(PsiSwitchLabelStatement.class).toSet();
Set<PsiSwitchLabelStatement> falseLabels = StreamEx.of(falseSet).select(BranchingInstruction.class)
.map(BranchingInstruction::getPsiAnchor).select(PsiSwitchLabelStatement.class).toSet();
Set<PsiSwitchBlock> coveredSwitches = new HashSet<>();
Set<PsiExpression> trueLabels = StreamEx.of(trueSet).select(ConditionalGotoInstruction.class)
.map(ConditionalGotoInstruction::getPsiAnchor).select(PsiExpression.class)
.filter(e -> SwitchUtils.getLabelStatementForLabel(e) != null).toSet();
Set<PsiExpression> falseLabels = StreamEx.of(falseSet).select(ConditionalGotoInstruction.class)
.map(ConditionalGotoInstruction::getPsiAnchor).select(PsiExpression.class)
.filter(e -> SwitchUtils.getLabelStatementForLabel(e) != null).toSet();
for (PsiSwitchLabelStatement label : trueLabels) {
PsiSwitchStatement statement = label.getEnclosingSwitchStatement();
for (PsiExpression label : trueLabels) {
PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(SwitchUtils.getLabelStatementForLabel(label));
PsiSwitchBlock statement = labelStatement.getEnclosingSwitchBlock();
if (statement == null) continue;
if (!StreamEx.iterate(label, Objects::nonNull, l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiSwitchLabelStatement.class))
.skip(1).allMatch(falseLabels::contains)) {
if (!StreamEx.iterate(labelStatement, Objects::nonNull, l -> PsiTreeUtil.getPrevSiblingOfType(l, PsiSwitchLabelStatement.class))
.skip(1).map(PsiSwitchLabelStatementBase::getCaseValues)
.nonNull().flatArray(PsiExpressionList::getExpressions).allMatch(falseLabels::contains)) {
continue;
}
coveredSwitches.add(statement);
holder.registerProblem(label, InspectionsBundle.message("dataflow.message.only.switch.label"),
createUnwrapSwitchLabelFix());
}
for (PsiSwitchLabelStatement label : falseLabels) {
if (!coveredSwitches.contains(label.getEnclosingSwitchStatement())) {
for (PsiExpression label : falseLabels) {
PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(SwitchUtils.getLabelStatementForLabel(label));
if (!coveredSwitches.contains(labelStatement.getEnclosingSwitchBlock())) {
holder.registerProblem(label, InspectionsBundle.message("dataflow.message.unreachable.switch.label"),
new DeleteSwitchLabelFix(label));
}
@@ -724,7 +729,9 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
reportConstantBoolean(holder, psiAnchor, reportedAnchors, true);
}
}
else if (psiAnchor != null && !(psiAnchor instanceof PsiSwitchLabelStatement) && !isFlagCheck(psiAnchor)) {
else if (psiAnchor != null &&
(!(psiAnchor instanceof PsiExpression) || SwitchUtils.getLabelStatementForLabel((PsiExpression)psiAnchor) == null) &&
!isFlagCheck(psiAnchor)) {
boolean evaluatesToTrue = trueSet.contains(instruction);
final PsiElement parent = psiAnchor.getParent();
if (parent instanceof PsiAssignmentExpression &&
@@ -11,6 +11,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ControlFlowUtils;
import com.siyeh.ig.psiutils.SwitchUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -24,12 +25,16 @@ public class DeleteSwitchLabelFix implements LocalQuickFix {
private final String myName;
private final boolean myBranch;
public DeleteSwitchLabelFix(PsiSwitchLabelStatement label) {
myName = Objects.requireNonNull(label.getCaseValue()).getText();
myBranch = shouldRemoveBranch(label);
public DeleteSwitchLabelFix(@NotNull PsiExpression label) {
myName = label.getText();
PsiSwitchLabelStatementBase labelStatement = Objects.requireNonNull(SwitchUtils.getLabelStatementForLabel(label));
PsiExpressionList values = labelStatement.getCaseValues();
boolean multiple = values != null && values.getExpressionCount() > 1;
myBranch = !multiple && shouldRemoveBranch(labelStatement);
}
private static boolean shouldRemoveBranch(PsiSwitchLabelStatement label) {
private static boolean shouldRemoveBranch(PsiSwitchLabelStatementBase label) {
if (label instanceof PsiSwitchLabeledRuleStatement) return true;
PsiStatement nextStatement = PsiTreeUtil.getNextSiblingOfType(label, PsiStatement.class);
if (nextStatement instanceof PsiSwitchLabelStatement) {
return false;
@@ -56,16 +61,23 @@ public class DeleteSwitchLabelFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiSwitchLabelStatement label = PsiTreeUtil.getNonStrictParentOfType(descriptor.getStartElement(), PsiSwitchLabelStatement.class);
PsiExpression expression = ObjectUtils.tryCast(descriptor.getStartElement(), PsiExpression.class);
if (expression == null) return;
PsiSwitchLabelStatementBase label = SwitchUtils.getLabelStatementForLabel(expression);
if (label == null) return;
deleteLabel(label);
PsiExpressionList values = label.getCaseValues();
if (values != null && values.getExpressionCount() == 1) {
deleteLabel(label);
} else {
new CommentTracker().deleteAndRestoreComments(expression);
}
}
public static void deleteLabel(PsiSwitchLabelStatement label) {
public static void deleteLabel(PsiSwitchLabelStatementBase label) {
if (shouldRemoveBranch(label)) {
PsiCodeBlock scope = ObjectUtils.tryCast(label.getParent(), PsiCodeBlock.class);
if (scope == null) return;
PsiSwitchLabelStatement nextLabel = PsiTreeUtil.getNextSiblingOfType(label, PsiSwitchLabelStatement.class);
PsiSwitchLabelStatementBase nextLabel = PsiTreeUtil.getNextSiblingOfType(label, PsiSwitchLabelStatementBase.class);
PsiElement stopAt = nextLabel == null ? scope.getRBrace() : nextLabel;
while(true) {
PsiStatement next = PsiTreeUtil.getNextSiblingOfType(nextLabel, PsiStatement.class);
@@ -6,12 +6,11 @@ import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.dataFlow.fix.DeleteSwitchLabelFix;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiSwitchLabelStatement;
import com.intellij.psi.PsiSwitchStatement;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.SwitchUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -27,17 +26,19 @@ public class UnwrapSwitchLabelFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiSwitchLabelStatement label = ObjectUtils.tryCast(descriptor.getStartElement(), PsiSwitchLabelStatement.class);
PsiExpression label = ObjectUtils.tryCast(descriptor.getStartElement(), PsiExpression.class);
if (label == null) return;
PsiSwitchStatement statement = label.getEnclosingSwitchStatement();
PsiSwitchLabelStatementBase labelStatement = SwitchUtils.getLabelStatementForLabel(label);
if (labelStatement == null) return;
PsiSwitchStatement statement = labelStatement.getEnclosingSwitchStatement();
if (statement == null) return;
List<PsiSwitchLabelStatement> labels = PsiTreeUtil.getChildrenOfTypeAsList(statement.getBody(), PsiSwitchLabelStatement.class);
for (PsiSwitchLabelStatement otherLabel : labels) {
if (otherLabel != label) {
if (otherLabel != labelStatement) {
DeleteSwitchLabelFix.deleteLabel(otherLabel);
}
}
new CommentTracker().replaceAndRestoreComments(label, "default:");
new CommentTracker().replaceAndRestoreComments(labelStatement, "default:");
ConvertSwitchToIfIntention.doProcessIntention(statement); // will not create 'if', just unwrap, because only default label is left
}
}
@@ -0,0 +1,11 @@
// "Remove switch label '0-/*x*/1'" "true"
class Main {
static void fff(int x) {
if (x > 0) {
switch (x) {
case 1, 3 /*x*/: System.out.println("one"); //1
case 2: System.out.println("two"); //2
}
}
}
}
@@ -0,0 +1,12 @@
// "Remove switch branch '-/*x*/1'" "true"
class Main {
static void fff(int x) {
if (x > 0) {
switch (x) {
case 2 -> System.out.println("two"); //2
//1
/*x*/
}
}
}
}
@@ -0,0 +1,19 @@
// "Unwrap 'switch' statement" "true"
class Main {
static void fff(int x) {
if (x == 5) {
//1
//2
//3
//4
System.out.println("five-ten-fifteen"); //5
System.out.println("six"); //6
System.out.println("seven"); //7
//other
}
}
public static void main(String[] args) {
fff();
}
}
@@ -0,0 +1,11 @@
// "Remove switch label '0-/*x*/1'" "true"
class Main {
static void fff(int x) {
if (x > 0) {
switch (x) {
case 1, 3, 0-<caret>/*x*/1: System.out.println("one"); //1
case 2: System.out.println("two"); //2
}
}
}
}
@@ -0,0 +1,11 @@
// "Remove switch branch '-/*x*/1'" "true"
class Main {
static void fff(int x) {
if (x > 0) {
switch (x) {
case 2 -> System.out.println("two"); //2
case -<caret>/*x*/1 -> System.out.println("one"); //1
}
}
}
}
@@ -3,7 +3,7 @@ class Main {
void t() {
int i = 5;
switch(i) {
c<caret>ase 1: case 3: // Apply 'Fix all problems in the file'
case <caret>1: case 3: // Apply 'Fix all problems in the file'
System.out.println("odd");
break;
}
@@ -0,0 +1,22 @@
// "Unwrap 'switch' statement" "true"
class Main {
static void fff(int x) {
if (x == 5) {
switch (x) {
case 1: System.out.println("one"); //1
case 2: System.out.println("two"); //2
case 3: System.out.println("three"); //3
case 4: System.out.println("four"); //4
case 0, <caret>5, 10: System.out.println("five-ten-fifteen"); //5
case 6: System.out.println("six"); //6
case 7: System.out.println("seven"); //7
break;
default: System.out.println("and more"); //other
}
}
}
public static void main(String[] args) {
fff();
}
}
@@ -37,8 +37,8 @@ public class aaa {
//System.exit(0);
switch(i) {
<warning descr="Switch label 'case 1:' is unreachable">case 1:</warning> System.out.println("1 not reachable"); break;
<warning descr="Switch label 'case 2:' is unreachable">case 2:</warning> System.out.println("2 not reachable"); break;
case <warning descr="Switch label '1' is unreachable">1</warning>: System.out.println("1 not reachable"); break;
case <warning descr="Switch label '2' is unreachable">2</warning>: System.out.println("2 not reachable"); break;
case 6: System.out.println("6 reachable"); break;
case 5: System.out.println("5 reachable"); break;
default: System.out.println("Default not reachable"); break;
@@ -1,7 +1,7 @@
class Scratch {
public static void main(String[] args) {
switch("ping") {
<warning descr="Switch label 'case \"ping\":' is the only reachable in the whole switch">case "ping":</warning>
case <warning descr="Switch label '\"ping\"' is the only reachable in the whole switch">"ping"</warning>:
System.out.println("ping");
break;
case "pong":
@@ -17,7 +17,7 @@ class Scratch {
case "pong":
System.out.println("pong");
break;
<warning descr="Switch label 'case \"ping\":' is the only reachable in the whole switch">case "ping":</warning>
case <warning descr="Switch label '\"ping\"' is the only reachable in the whole switch">"ping"</warning>:
System.out.println("ping");
break;
case "simple":
@@ -33,7 +33,7 @@ class Scratch {
case "simple":
System.out.println("simple");
break;
<warning descr="Switch label 'case \"ping\":' is the only reachable in the whole switch">case "ping":</warning>
case <warning descr="Switch label '\"ping\"' is the only reachable in the whole switch">"ping"</warning>:
System.out.println("ping");
break;
default:
@@ -160,7 +160,7 @@ public class LongRangeBasics {
public void testBitwiseAnd() {
int state = getState() & 0xF;
switch (state) {
<warning descr="Switch label 'case 24:' is unreachable">case 24:</warning>
case <warning descr="Switch label '24' is unreachable">24</warning>:
System.out.println("Impossible");
}
}
@@ -17,7 +17,7 @@ class StringEquality {
switch(s) {
case "bar":
case "baz":
<warning descr="Switch label 'case \"foo\":' is unreachable">case "foo":</warning>
case <warning descr="Switch label '\"foo\"' is unreachable">"foo"</warning>:
}
}
@@ -51,7 +51,7 @@ class InspectionTest {
System.out.println("It's b");break;
case C:
System.out.println("It's c");break;
<warning descr="Switch label 'case A:' is unreachable">case A:</warning>
case <warning descr="Switch label 'A' is unreachable">A</warning>:
System.out.println("It's a again");break;
}
}
@@ -0,0 +1,42 @@
public class SwitchStatementsJava12 {
void testMultiLabel(int x) {
if(x > 0) {
switch (x) {
case 1, 2, <warning descr="Switch label '-1' is unreachable">-1</warning>:
System.out.println("oops");
break;
}
switch (x) {
case 1, 2, <warning descr="Switch label '-1' is unreachable">-1</warning> -> {
System.out.println("oops");
break;
}
}
}
}
void testThrowRule(int x) {
switch (x) {
case 0 -> throw new IllegalArgumentException();
default -> System.out.println(<warning descr="Condition 'x == 0' is always 'false'">x == 0</warning>);
}
if (<warning descr="Condition 'x == 0' is always 'false'">x == 0</warning>) System.out.println("impossible");
}
void testFallthrough(int x) {
switch (x) {
case 0 -> System.out.println(x);
case 1 -> {
System.out.println(<warning descr="Condition 'x == 0' is always 'false'">x == 0</warning>);
System.out.println(<warning descr="Condition 'x == 1' is always 'true'">x == 1</warning>);
}
}
switch (x) {
case 0: System.out.println(x);
case 1: {
System.out.println(x == 0);
System.out.println(x == 1);
}
}
}
}
@@ -0,0 +1,42 @@
// 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.java.codeInspection;
import com.intellij.JavaTestUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
import org.jetbrains.annotations.NotNull;
public class DataFlowInspection12Test extends DataFlowInspectionTestCase {
private static final DefaultLightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@Override
public Sdk getSdk() {
return PsiTestUtil.addJdkAnnotations(IdeaTestUtil.getMockJdk(LanguageLevel.JDK_12_PREVIEW.toJavaVersion()));
}
@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.JDK_12_PREVIEW);
}
};
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return PROJECT_DESCRIPTOR;
}
@Override
protected String getTestDataPath() {
return JavaTestUtil.getJavaTestDataPath() + "/inspection/dataFlow/fixture/";
}
public void testSwitchStatementsJava12() { doTest(); }
}
@@ -16,7 +16,7 @@ import java.util.regex.Pattern;
* A TestCase for single PsiFile being opened in Editor conversion. See configureXXX and checkResultXXX method docs.
*/
public abstract class LightCodeInsightTestCase extends LightPlatformCodeInsightTestCase {
private static final Pattern JDK_SELECT_PATTERN = Pattern.compile("Java([\\d.]+)(\\.java)?$");
private static final Pattern JDK_SELECT_PATTERN = Pattern.compile("Java([\\d.]+)(Preview)?(\\.java)?$");
public static JavaPsiFacadeEx getJavaFacade() {
return JavaPsiFacadeEx.getInstanceEx(ourProject);
@@ -42,6 +42,9 @@ public abstract class LightCodeInsightTestCase extends LightPlatformCodeInsightT
if (matcher.find()) {
LanguageLevel level = LanguageLevel.parse(matcher.group(1));
if (level != null) {
if (!matcher.group(2).isEmpty()) {
level = LanguageLevel.valueOf(level + "_PREVIEW");
}
return level;
}
}
@@ -23,6 +23,8 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -239,6 +241,21 @@ public class SwitchUtils {
}
}
/**
* Returns enclosing label statement for given label expression
*
* @param expression switch label expression
* @return enclosing label statement or null if given expression is not a label statement
*/
@Contract("null -> null")
@Nullable
public static PsiSwitchLabelStatementBase getLabelStatementForLabel(PsiExpression expression) {
if (expression == null) return null;
PsiElement parent = expression.getParent();
if (!(parent instanceof PsiExpressionList)) return null;
return ObjectUtils.tryCast(parent.getParent(), PsiSwitchLabelStatementBase.class);
}
private static boolean checkForLabel(String name, PsiElement ancestor) {
final LabelSearchVisitor visitor = new LabelSearchVisitor(name);
ancestor.accept(visitor);