mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: Don't extract method where it's not safe because of nullable result (IDEA-167391)
This commit is contained in:
+71
-20
@@ -27,6 +27,7 @@ import com.intellij.codeInsight.intention.impl.AddNullableNotNullAnnotationFix;
|
||||
import com.intellij.codeInsight.navigation.NavigationUtil;
|
||||
import com.intellij.codeInspection.dataFlow.*;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.BranchingInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.CheckReturnValueInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
@@ -75,6 +76,7 @@ import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.VisibilityUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -245,7 +247,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
}
|
||||
catch (ControlFlowWrapper.ExitStatementsNotSameException e) {
|
||||
myExitStatements = myControlFlowWrapper.getExitStatements();
|
||||
myNotNullConditionalCheck = areAllExitPointsAreNotNull(getExpectedReturnType());
|
||||
myNotNullConditionalCheck = areAllExitPointsNotNull(getExpectedReturnType());
|
||||
if (!myNotNullConditionalCheck) {
|
||||
showMultipleExitPointsMessage();
|
||||
return false;
|
||||
@@ -280,17 +282,9 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
|
||||
if (myGenerateConditionalExit && myOutputVariables.length == 1) {
|
||||
if (!(myOutputVariables[0].getType() instanceof PsiPrimitiveType)) {
|
||||
myNullConditionalCheck = true;
|
||||
for (PsiStatement exitStatement : myExitStatements) {
|
||||
if (exitStatement instanceof PsiReturnStatement) {
|
||||
final PsiExpression returnValue = ((PsiReturnStatement)exitStatement).getReturnValue();
|
||||
myNullConditionalCheck &= returnValue == null || isNullInferred(returnValue.getText(), true);
|
||||
}
|
||||
}
|
||||
myNullConditionalCheck &= isNullInferred(myOutputVariables[0].getName(), false);
|
||||
myNullConditionalCheck = isNullInferred(myOutputVariables[0].getName()) && getReturnsNullability(true);
|
||||
}
|
||||
|
||||
myNotNullConditionalCheck = areAllExitPointsAreNotNull(returnStatementType);
|
||||
myNotNullConditionalCheck = areAllExitPointsNotNull(returnStatementType);
|
||||
}
|
||||
|
||||
if (!myHasReturnStatementOutput && checkOutputVariablesCount() && !myNullConditionalCheck && !myNotNullConditionalCheck) {
|
||||
@@ -385,16 +379,73 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean areAllExitPointsAreNotNull(PsiType returnStatementType) {
|
||||
private boolean areAllExitPointsNotNull(PsiType returnStatementType) {
|
||||
if (insertNotNullCheckIfPossible() && myControlFlowWrapper.getOutputVariables(false).length == 0) {
|
||||
boolean isNotNull = returnStatementType != null && !PsiType.VOID.equals(returnStatementType);
|
||||
for (PsiStatement statement : myExitStatements) {
|
||||
if (statement instanceof PsiReturnStatement) {
|
||||
final PsiExpression returnValue = ((PsiReturnStatement)statement).getReturnValue();
|
||||
isNotNull &= returnValue != null && !isNullInferred(returnValue.getText(), true);
|
||||
if (returnStatementType != null && !PsiType.VOID.equals(returnStatementType)) {
|
||||
return getReturnsNullability(false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nullsExpected when true check that all returned values are null, when false check that all returned values can't be null
|
||||
*/
|
||||
private boolean getReturnsNullability(boolean nullsExpected) {
|
||||
PsiElement body = null;
|
||||
if (myCodeFragmentMember instanceof PsiMethod) {
|
||||
body = ((PsiMethod)myCodeFragmentMember).getBody();
|
||||
}
|
||||
else if (myCodeFragmentMember instanceof PsiLambdaExpression) {
|
||||
body = ((PsiLambdaExpression)myCodeFragmentMember).getBody();
|
||||
}
|
||||
if (body == null) return false;
|
||||
|
||||
Set<PsiExpression> returnedExpressions = StreamEx.of(myExitStatements)
|
||||
.select(PsiReturnStatement.class)
|
||||
.map(PsiReturnStatement::getReturnValue)
|
||||
.nonNull()
|
||||
.toSet();
|
||||
|
||||
for (Iterator<PsiExpression> it = returnedExpressions.iterator(); it.hasNext(); ) {
|
||||
PsiType type = it.next().getType();
|
||||
if (nullsExpected) {
|
||||
if (type == PsiType.NULL) {
|
||||
it.remove(); // don't need to check
|
||||
}
|
||||
else if (type instanceof PsiPrimitiveType) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isNotNull;
|
||||
else {
|
||||
if (type == PsiType.NULL) {
|
||||
return false;
|
||||
}
|
||||
else if (type instanceof PsiPrimitiveType) {
|
||||
it.remove(); // don't need to check
|
||||
}
|
||||
}
|
||||
}
|
||||
if (returnedExpressions.isEmpty()) return true;
|
||||
|
||||
class ReturnChecker extends StandardInstructionVisitor {
|
||||
boolean myResult = true;
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] visitCheckReturnValue(CheckReturnValueInstruction instruction,
|
||||
DataFlowRunner runner,
|
||||
DfaMemoryState memState) {
|
||||
PsiElement aReturn = instruction.getReturn();
|
||||
if (aReturn instanceof PsiExpression && returnedExpressions.contains(aReturn)) {
|
||||
myResult &= nullsExpected ? memState.isNull(memState.peek()) : memState.isNotNull(memState.peek());
|
||||
}
|
||||
return super.visitCheckReturnValue(instruction, runner, memState);
|
||||
}
|
||||
}
|
||||
final StandardDataFlowRunner dfaRunner = new StandardDataFlowRunner();
|
||||
final ReturnChecker returnChecker = new ReturnChecker();
|
||||
if (dfaRunner.analyzeMethod(body, returnChecker) == RunnerResult.OK) {
|
||||
return returnChecker.myResult;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -403,7 +454,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isNullInferred(String exprText, boolean trueSet) {
|
||||
private boolean isNullInferred(String exprText) {
|
||||
final PsiCodeBlock block = myElementFactory.createCodeBlockFromText("{}", myElements[0]);
|
||||
for (PsiElement element : myElements) {
|
||||
block.add(element);
|
||||
@@ -416,7 +467,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
final RunnerResult rc = dfaRunner.analyzeMethod(block, visitor);
|
||||
if (rc == RunnerResult.OK) {
|
||||
final Pair<Set<Instruction>, Set<Instruction>> expressions = dfaRunner.getConstConditionalExpressions();
|
||||
final Set<Instruction> set = trueSet ? expressions.getFirst() : expressions.getSecond();
|
||||
final Set<Instruction> set = expressions.getSecond();
|
||||
for (Instruction instruction : set) {
|
||||
if (instruction instanceof BranchingInstruction) {
|
||||
if (((BranchingInstruction)instruction).getPsiAnchor().getText().equals(statementFromText.getCondition().getText())) {
|
||||
|
||||
Reference in New Issue
Block a user