IDEA-202132 Control flow for switch expressions: remove support of return in lambda (unsupported by spec); refactoring

This commit is contained in:
Tagir Valeev
2018-11-20 18:07:39 +07:00
parent 3c5eb063a9
commit bce1f3ea1b
4 changed files with 72 additions and 201 deletions
@@ -707,32 +707,6 @@ public class CFGBuilder {
}
}
/**
* Return true if given expression contains return statement (which could be inside switch expression)
* @param expression expression to analyze
* @return true if it has nested return
*/
private static boolean hasNestedReturn(@NotNull PsiExpression expression) {
class Visitor extends JavaRecursiveElementWalkingVisitor {
boolean hasReturn;
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {}
@Override
public void visitClass(PsiClass aClass) {}
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
hasReturn = true;
stopWalking();
}
}
Visitor visitor = new Visitor();
expression.accept(visitor);
return visitor.hasReturn;
}
/**
* Inlines given lambda. Lambda parameters are assumed to be assigned already (if necessary).
* <p>
@@ -748,20 +722,14 @@ public class CFGBuilder {
PsiElement body = lambda.getBody();
PsiExpression expression = LambdaUtil.extractSingleExpressionFromBody(body);
if (expression != null) {
if (hasNestedReturn(expression)) {
DfaVariableValue variable = createTempVariable(LambdaUtil.getFunctionalInterfaceReturnType(lambda));
myAnalyzer.inlineExpression(lambda, expression, resultNullability, variable);
push(variable);
} else {
pushExpression(expression);
boxUnbox(expression, LambdaUtil.getFunctionalInterfaceReturnType(lambda));
if (resultNullability == Nullability.NOT_NULL) {
checkNotNull(expression, NullabilityProblemKind.nullableFunctionReturn);
}
pushExpression(expression);
boxUnbox(expression, LambdaUtil.getFunctionalInterfaceReturnType(lambda));
if (resultNullability == Nullability.NOT_NULL) {
checkNotNull(expression, NullabilityProblemKind.nullableFunctionReturn);
}
} else if(body instanceof PsiCodeBlock) {
DfaVariableValue variable = createTempVariable(LambdaUtil.getFunctionalInterfaceReturnType(lambda));
myAnalyzer.inlineBlock(lambda, (PsiCodeBlock)body, resultNullability, variable);
myAnalyzer.inlineBlock((PsiCodeBlock)body, resultNullability, variable);
push(variable);
} else {
pushUnknown();
@@ -801,24 +801,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
PsiExpression returnValue = statement.getReturnValue();
InlinedBlockContext context = myInlinedBlockContext;
while (context != null && context.isSwitch()) {
context = context.myPreviousBlock;
}
if (context != null) {
if (returnValue != null) {
DfaVariableValue var = context.myTarget;
addInstruction(new PushInstruction(var, null, true));
returnValue.accept(this);
generateBoxingUnboxingInstructionFor(returnValue, var.getType());
if (context.myForceNonNullBlockResult) {
addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.nullableFunctionReturn.problem(returnValue)));
}
addInstruction(new AssignInstruction(returnValue, null));
addInstruction(new PopInstruction());
}
controlTransfer(new InstructionTransfer(getEndOffset(context.myCodeBlock), getVariablesInside(context.myCodeBlock)),
getTrapsInsideElement(context.myCodeBlock));
// We treat return inside switch expression (which is disallowed syntax) as break-with-value
context.generateReturn(returnValue, this);
} else {
if (returnValue != null) {
@@ -850,52 +835,48 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
@Override
public void visitSwitchLabeledRuleStatement(PsiSwitchLabeledRuleStatement statement) {
PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock();
if (switchBlock == null) return;
startElement(statement);
PsiStatement body = statement.getBody();
PsiCodeBlock switchBody = switchBlock.getBody();
boolean expressionSwitch = myInlinedBlockContext != null && myInlinedBlockContext.myCodeBlock == switchBody;
if (expressionSwitch && body instanceof PsiExpressionStatement) {
myInlinedBlockContext.generateReturn(((PsiExpressionStatement)body).getExpression(), this);
}
if (body != null) {
if (body instanceof PsiExpressionStatement && myInlinedBlockContext != null &&
myInlinedBlockContext.myCodeBlock == statement.getEnclosingSwitchBlock()) {
addInstruction(new PushInstruction(myInlinedBlockContext.myTarget, null, true));
PsiExpression expression = ((PsiExpressionStatement)body).getExpression();
expression.accept(this);
generateBoxingUnboxingInstructionFor(expression, myInlinedBlockContext.myTarget.getType());
addInstruction(new AssignInstruction(null, myInlinedBlockContext.myTarget));
addInstruction(new PopInstruction());
} else {
body.accept(this);
}
if (!(body instanceof PsiThrowStatement)) {
jumpOut(statement.getEnclosingSwitchBlock());
}
body.accept(this);
}
if (!(body instanceof PsiThrowStatement)) {
jumpOut(expressionSwitch ? switchBody : switchBlock);
}
finishElement(statement);
}
@Override public void visitSwitchStatement(PsiSwitchStatement switchStmt) {
processSwitch(switchStmt, null);
startElement(switchStmt);
processSwitch(switchStmt);
finishElement(switchStmt);
}
@Override
public void visitSwitchExpression(PsiSwitchExpression expression) {
PsiCodeBlock body = expression.getBody();
if (body == null) {
PsiExpression selector = expression.getExpression();
if (selector != null) {
selector.accept(this);
}
addInstruction(new PopInstruction());
processSwitch(expression);
pushUnknown();
} else {
startElement(expression);
DfaVariableValue resultVariable = createTempVariable(expression.getType());
enterInlinedBlock(expression, Nullability.UNKNOWN, resultVariable);
processSwitch(expression, resultVariable);
enterInlinedBlock(body, Nullability.UNKNOWN, resultVariable);
processSwitch(expression);
exitInlinedBlock();
addInstruction(new PushInstruction(resultVariable, expression));
finishElement(expression);
}
}
private void processSwitch(@NotNull PsiSwitchBlock switchBlock, @Nullable DfaVariableValue resultVariable) {
startElement(switchBlock);
private void processSwitch(@NotNull PsiSwitchBlock switchBlock) {
PsiExpression caseExpression = switchBlock.getExpression();
Set<PsiEnumConstant> enumValues = null;
DfaVariableValue expressionValue = null;
@@ -990,7 +971,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (expressionValue != null) {
addInstruction(new FlushVariableInstruction(expressionValue));
}
finishElement(switchBlock);
}
@Override
@@ -2058,35 +2038,24 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
* @param resultNullability desired nullability returned by block return statement
* @param target a variable to store the block result (returned via {@code return} statement)
*/
void inlineBlock(@NotNull PsiElement anchor, @NotNull PsiCodeBlock block, @NotNull Nullability resultNullability, @NotNull DfaVariableValue target) {
enterInlinedBlock(anchor, resultNullability, target);
startElement(anchor);
void inlineBlock(@NotNull PsiCodeBlock block, @NotNull Nullability resultNullability, @NotNull DfaVariableValue target) {
enterInlinedBlock(block, resultNullability, target);
block.accept(this);
finishElement(anchor);
exitInlinedBlock();
}
void inlineExpression(@NotNull PsiElement anchor, @NotNull PsiExpression expr, @NotNull Nullability resultNullability, @NotNull DfaVariableValue target) {
enterInlinedBlock(anchor, resultNullability, target);
startElement(anchor);
addInstruction(new PushInstruction(target, null, true));
expr.accept(this);
addInstruction(new AssignInstruction(null, target));
addInstruction(new PopInstruction());
finishElement(anchor);
exitInlinedBlock();
}
private void enterInlinedBlock(@NotNull PsiElement block,
private void enterInlinedBlock(@NotNull PsiCodeBlock block,
@NotNull Nullability resultNullability,
@NotNull DfaVariableValue target) {
// Transfer value is pushed to avoid emptying stack beyond this point
pushTrap(new Trap.InsideInlinedBlock(block));
addInstruction(new PushInstruction(myFactory.controlTransfer(ReturnTransfer.INSTANCE, FList.emptyList()), null));
myInlinedBlockContext = new InlinedBlockContext(myInlinedBlockContext, block, resultNullability == Nullability.NOT_NULL, target);
startElement(block);
}
private void exitInlinedBlock() {
finishElement(myInlinedBlockContext.myCodeBlock);
myInlinedBlockContext = myInlinedBlockContext.myPreviousBlock;
popTrap(Trap.InsideInlinedBlock.class);
// Pop transfer value
@@ -2144,24 +2113,39 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
}
public static class InlinedBlockContext {
static class InlinedBlockContext {
final InlinedBlockContext myPreviousBlock;
final @NotNull PsiElement myCodeBlock; // either PsiLambdaExpression or PsiSwitchExpression currently
final @NotNull PsiCodeBlock myCodeBlock;
final boolean myForceNonNullBlockResult;
final @NotNull DfaVariableValue myTarget;
public InlinedBlockContext(InlinedBlockContext previousBlock,
@NotNull PsiElement codeBlock,
boolean forceNonNullBlockResult,
@NotNull DfaVariableValue target) {
InlinedBlockContext(InlinedBlockContext previousBlock,
@NotNull PsiCodeBlock codeBlock,
boolean forceNonNullBlockResult,
@NotNull DfaVariableValue target) {
myPreviousBlock = previousBlock;
myCodeBlock = codeBlock;
myForceNonNullBlockResult = forceNonNullBlockResult;
myTarget = target;
}
public boolean isSwitch() {
return myCodeBlock instanceof PsiSwitchExpression;
boolean isSwitch() {
return myCodeBlock.getParent() instanceof PsiSwitchExpression;
}
void generateReturn(PsiExpression returnValue, ControlFlowAnalyzer analyzer) {
if (returnValue != null) {
analyzer.addInstruction(new PushInstruction(myTarget, null, true));
returnValue.accept(analyzer);
analyzer.generateBoxingUnboxingInstructionFor(returnValue, myTarget.getType());
if (myForceNonNullBlockResult) {
analyzer.addInstruction(new CheckNotNullInstruction(NullabilityProblemKind.nullableFunctionReturn.problem(returnValue)));
}
analyzer.addInstruction(new AssignInstruction(returnValue, null));
analyzer.addInstruction(new PopInstruction());
}
analyzer.jumpOut(myCodeBlock);
}
}
@@ -124,7 +124,7 @@ sealed class Trap(val anchor: PsiElement) {
return handler.dispatch()
}
}
class InsideInlinedBlock(block: PsiElement): Trap(block) {
class InsideInlinedBlock(block: PsiCodeBlock): Trap(block) {
override fun dispatch(handler: ControlTransferHandler): List<DfaInstructionState> {
(handler.state.pop() as DfaControlTransferValue).target as ReturnTransfer
return handler.dispatch()
@@ -55,103 +55,22 @@ public class SwitchExpressionsJava12 {
if (<warning descr="Condition 'i < 1' is always 'false'">i < 1</warning>) {}
}
static void testSwitchInInlinedLambda(int i) {
int x = ((IntSupplier)() -> {
int j = switch (i) {
case 1 -> 2;
case 2 -> 3;
case 3 -> {
System.out.println("hello");
return 1; // exit lambda, not switch!
}
default -> 4;
static void testIncomplete(Integer i) {
if (i == null) {
System.out.println(switch (<warning descr="Unboxing of 'i' may produce 'NullPointerException'">i</warning>)<error descr="'{' expected">)</error>;
} else {
int x = switch(i) {
case 1 -><EOLError descr="Expression, block or throw statement expected"></EOLError>
case 2 -> {
if (<warning descr="Condition 'i == 1' is always 'false'">i == 1</warning>) {}
throw new IllegalArgumentException();
}
default -> 1;
};
if (<warning descr="Condition 'j < 2' is always 'false'">j < 2</warning>) {}
if (<warning descr="Condition 'j == 4 && i == 2' is always 'false'">j == 4 && <warning descr="Condition 'i == 2' is always 'false' when reached">i == 2</warning></warning>) {}
if (<warning descr="Condition 'i == 3' is always 'false'">i == 3</warning>) {}
return 0;
}).getAsInt();
if (x == 1 && <warning descr="Condition 'i == 3' is always 'true' when reached">i == 3</warning>) {}
}
static void testSwitchInInlinedLambdaWithInnerFinally(int i) {
int x = ((IntSupplier)() -> {
int j = switch (i) {
case 1 -> 2;
case 2 -> 3;
case 3 -> {
try {
System.out.println("hello");
return 1;
}
finally {
return 2;
}
}
default -> 4;
};
if (<warning descr="Condition 'j < 2' is always 'false'">j < 2</warning>) {}
if (<warning descr="Condition 'j == 4 && i == 2' is always 'false'">j == 4 && <warning descr="Condition 'i == 2' is always 'false' when reached">i == 2</warning></warning>) {}
if (<warning descr="Condition 'i == 3' is always 'false'">i == 3</warning>) {}
return 0;
}).getAsInt();
if (<warning descr="Condition 'x == 1 && i == 3' is always 'false'"><warning descr="Condition 'x == 1' is always 'false'">x == 1</warning> && i == 3</warning>) {}
if (x == 2 && <warning descr="Condition 'i == 3' is always 'true' when reached">i == 3</warning>) {}
}
static void testSwitchInInlinedLambdaWithOuterFinally(int i) {
int x = ((IntSupplier)() -> {
int j = 0;
try {
j = switch (i) {
case 1 -> 2;
case 2 -> 3;
case 3 -> {
System.out.println("hello");
return 1;
}
default -> 4;
};
if (x != 1) {
// the only case where we don't know the returned value is i == 1, so the warning is logical here
if (<warning descr="Condition 'i == 1' is always 'true'">i == 1</warning>) {}
}
finally {
if (j == 0) {
return 2;
}
}
if (<warning descr="Condition 'j < 2' is always 'false'">j < 2</warning>) {}
if (<warning descr="Condition 'j == 4 && i == 2' is always 'false'">j == 4 && <warning descr="Condition 'i == 2' is always 'false' when reached">i == 2</warning></warning>) {}
if (<warning descr="Condition 'i == 3' is always 'false'">i == 3</warning>) {}
return 0;
}).getAsInt();
if (<warning descr="Condition 'x == 1 && i == 3' is always 'false'"><warning descr="Condition 'x == 1' is always 'false'">x == 1</warning> && i == 3</warning>) {}
if (x == 2 && <warning descr="Condition 'i == 3' is always 'true' when reached">i == 3</warning>) {}
}
static int get(int x) {
return x;
}
void testStatementInsideExpressionInsideBlockLambda(int x, int y) {
int i = ((IntSupplier)() -> {
System.out.println();
return switch(x) {
case 1 -> {
switch (y) {
default -> get(x);
}
return 5;
}
default -> 10;
};
}).getAsInt();
if (i != 10) {}
}
void testSwitchWithReturnInsideExpressionLambda(int x) {
int i = ((IntSupplier)() -> x < 0 ? 0 : switch(x) {
case 1 -> { return 2; }
default -> 3;
}).getAsInt();
if (i == 2 && <warning descr="Condition 'x == 1' is always 'true' when reached">x == 1</warning>) {}
}
}
}