mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-15 11:12:59 +07:00
DFA: support partial streams and ::isInstance method reference
Fixes: IDEA-182519 Support non-terminated Stream API chains in data flow IDEA-182520 Support Xyz.class::isInstance in stream/optional chains
This commit is contained in:
@@ -446,6 +446,9 @@ public class GuessManagerImpl extends GuessManager {
|
||||
|
||||
@Override
|
||||
public DfaInstructionState[] visitInstanceof(InstanceofInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
|
||||
if (instruction.getLeft() == null) {
|
||||
return super.visitInstanceof(instruction, runner, memState);
|
||||
}
|
||||
DfaValue type = memState.pop();
|
||||
DfaValue operand = memState.pop();
|
||||
DfaValue relation = runner.getFactory().createCondition(operand, DfaRelationValue.RelationType.IS, type);
|
||||
|
||||
@@ -498,6 +498,7 @@ public class CFGBuilder {
|
||||
JavaResolveResult resolveResult = methodRef.advancedResolve(false);
|
||||
PsiMethod method = ObjectUtils.tryCast(resolveResult.getElement(), PsiMethod.class);
|
||||
if (method != null && !method.isVarArgs()) {
|
||||
if (processKnownMethodReference(argCount, methodRef, method)) return this;
|
||||
int expectedArgCount = method.getParameterList().getParametersCount();
|
||||
boolean pushQualifier = true;
|
||||
if (!method.hasModifierProperty(PsiModifier.STATIC) && !method.isConstructor()) {
|
||||
@@ -548,6 +549,17 @@ public class CFGBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean processKnownMethodReference(int argCount, PsiMethodReferenceExpression methodRef, PsiMethod method) {
|
||||
if (argCount != 1 || !method.getName().equals("isInstance")) return false;
|
||||
PsiClassObjectAccessExpression qualifier = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(methodRef.getQualifierExpression()),
|
||||
PsiClassObjectAccessExpression.class);
|
||||
if (qualifier == null) return false;
|
||||
PsiType type = qualifier.getOperand().getType();
|
||||
push(getFactory().createTypeValue(type, Nullness.NOT_NULL));
|
||||
myAnalyzer.addInstruction(new InstanceofInstruction(methodRef, methodRef.getProject(), null, type));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to move top stack value to the specified depth
|
||||
* <p>
|
||||
|
||||
+4
@@ -616,6 +616,10 @@ public class DataFlowInspectionBase extends AbstractBaseJavaLocalInspectionTool
|
||||
InspectionsBundle.message("dataflow.message.loop.on.empty.array") :
|
||||
InspectionsBundle.message("dataflow.message.loop.on.empty.collection"));
|
||||
}
|
||||
else if (psiAnchor instanceof PsiMethodReferenceExpression) {
|
||||
holder.registerProblem(psiAnchor, InspectionsBundle.message("dataflow.message.constant.method.reference", evaluatesToTrue),
|
||||
createReplaceWithTrivialLambdaFix(evaluatesToTrue));
|
||||
}
|
||||
else {
|
||||
boolean isAssertion = isAssertionEffectively(psiAnchor, evaluatesToTrue);
|
||||
if (!DONT_REPORT_TRUE_ASSERT_STATEMENTS || !isAssertion) {
|
||||
|
||||
+12
-7
@@ -19,10 +19,8 @@ import com.intellij.codeInspection.InspectionsBundle;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiInstanceOfExpression;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -38,10 +36,17 @@ public class RedundantInstanceofFix implements LocalQuickFix {
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
final PsiElement psiElement = descriptor.getPsiElement();
|
||||
String replacement;
|
||||
if (psiElement instanceof PsiInstanceOfExpression) {
|
||||
PsiExpression compareToNull = JavaPsiFacade.getInstance(psiElement.getProject()).getElementFactory().
|
||||
createExpressionFromText(((PsiInstanceOfExpression)psiElement).getOperand().getText() + " != null", psiElement.getParent());
|
||||
psiElement.replace(compareToNull);
|
||||
replacement = ((PsiInstanceOfExpression)psiElement).getOperand().getText() + " != null";
|
||||
}
|
||||
else if (psiElement instanceof PsiMethodReferenceExpression) {
|
||||
replacement = CommonClassNames.JAVA_UTIL_OBJECTS + "::nonNull";
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
PsiExpression compareToNull = JavaPsiFacade.getElementFactory(project).createExpressionFromText(replacement, psiElement.getParent());
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(psiElement.replace(compareToNull));
|
||||
}
|
||||
}
|
||||
|
||||
+41
-3
@@ -103,12 +103,29 @@ public class StreamChainInliner implements CallInliner {
|
||||
.register(MIN_MAX_TERMINAL, MinMaxTerminalStep::new)
|
||||
.register(OPTIONAL_TERMINAL, OptionalTerminalStep::new);
|
||||
|
||||
private static final Step NULL_TERMINAL_STEP = new Step(null, null, null) {
|
||||
@Override
|
||||
void before(CFGBuilder builder) {
|
||||
builder.flushFields();
|
||||
}
|
||||
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
builder.pop();
|
||||
}
|
||||
|
||||
@Override
|
||||
void pushResult(CFGBuilder builder) {
|
||||
builder.pushUnknown();
|
||||
}
|
||||
};
|
||||
|
||||
static abstract class Step {
|
||||
final Step myNext;
|
||||
final @NotNull PsiMethodCallExpression myCall;
|
||||
final PsiMethodCallExpression myCall;
|
||||
final PsiExpression myFunction;
|
||||
|
||||
Step(@NotNull PsiMethodCallExpression call, Step next, PsiExpression function) {
|
||||
Step(PsiMethodCallExpression call, Step next, PsiExpression function) {
|
||||
myNext = next;
|
||||
myCall = call;
|
||||
myFunction = function;
|
||||
@@ -480,9 +497,30 @@ public class StreamChainInliner implements CallInliner {
|
||||
|
||||
@Override
|
||||
public boolean tryInlineCall(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
|
||||
if (!TERMINAL_CALL.test(call)) {
|
||||
if (TERMINAL_CALL.test(call)) {
|
||||
return inlineCompleteStream(builder, call);
|
||||
}
|
||||
else {
|
||||
return inlinePartialStream(builder, call);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inlinePartialStream(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
|
||||
Step firstStep = buildChain(call, NULL_TERMINAL_STEP);
|
||||
if (firstStep == NULL_TERMINAL_STEP) {
|
||||
return false;
|
||||
}
|
||||
PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression();
|
||||
if (originalQualifier == null) return false;
|
||||
builder.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.chain(b -> buildStreamCFG(b, firstStep, originalQualifier))
|
||||
.endIf()
|
||||
.push(builder.getFactory().createTypeValue(call.getType(), Nullness.NOT_NULL));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean inlineCompleteStream(@NotNull CFGBuilder builder, @NotNull PsiMethodCallExpression call) {
|
||||
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
Step terminalStep = createTerminalStep(call);
|
||||
Step firstStep = buildChain(qualifierCall, terminalStep);
|
||||
|
||||
+8
-3
@@ -25,15 +25,16 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
public class InstanceofInstruction extends BinopInstruction {
|
||||
@NotNull private final PsiExpression myLeft;
|
||||
@Nullable private final PsiExpression myLeft;
|
||||
@NotNull private final PsiType myCastType;
|
||||
|
||||
public InstanceofInstruction(PsiElement psiAnchor, @NotNull Project project, @NotNull PsiExpression left, @NotNull PsiType castType) {
|
||||
public InstanceofInstruction(PsiElement psiAnchor, @NotNull Project project, @Nullable PsiExpression left, @NotNull PsiType castType) {
|
||||
super(JavaTokenType.INSTANCEOF_KEYWORD, psiAnchor, project);
|
||||
myLeft = left;
|
||||
myCastType = castType;
|
||||
@@ -44,7 +45,11 @@ public class InstanceofInstruction extends BinopInstruction {
|
||||
return visitor.visitInstanceof(this, runner, stateBefore);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
/**
|
||||
* @return instanceof operand or null if it's not applicable
|
||||
* (e.g. instruction is emitted when inlining Xyz.class::isInstance method reference)
|
||||
*/
|
||||
@Nullable
|
||||
public PsiExpression getLeft() {
|
||||
return myLeft;
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with a null check" "true"
|
||||
class Test {
|
||||
void test(String s) {
|
||||
Object obj = s;
|
||||
if(obj != null) {
|
||||
System.out.println("always");
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with a null check" "true"
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class Test {
|
||||
void test(Stream<String> s) {
|
||||
s.filter(Objects::nonNull)
|
||||
.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with a null check" "true"
|
||||
class Test {
|
||||
void test(String s) {
|
||||
Object obj = s;
|
||||
if(obj instanceof <caret>String) {
|
||||
System.out.println("always");
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Replace with a null check" "true"
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class Test {
|
||||
void test(Stream<String> s) {
|
||||
s.filter(String.class::<caret>isInstance)
|
||||
.forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,40 @@ public class StreamInlining {
|
||||
.forEach(System.out::println);
|
||||
}
|
||||
|
||||
void testIsInstanceIncomplete(List<?> objects) {
|
||||
IntStream is = objects.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.mapToInt(x -> (<warning descr="Casting 'x' to 'Integer' may produce 'java.lang.ClassCastException'">Integer</warning>)x);
|
||||
|
||||
objects.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.filter(<warning descr="Method reference result is always 'false'">Number.class::isInstance</warning>);
|
||||
|
||||
objects.stream()
|
||||
.filter(x -> x instanceof String)
|
||||
.filter(<warning descr="Method reference result is always 'false'">Number.class::isInstance</warning>);
|
||||
|
||||
objects.stream()
|
||||
.filter(String.class::isInstance)
|
||||
.filter(<warning descr="Method reference result is always 'true'">String.class::isInstance</warning>);
|
||||
}
|
||||
|
||||
Stream<String> testInstanceOfMap(List<?> objects) {
|
||||
return objects.stream().filter(it -> it instanceof String)
|
||||
.map(entry -> {
|
||||
if (<warning descr="Condition 'entry instanceof String' is always 'true'">entry instanceof String</warning>) {
|
||||
return (String)entry;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>);
|
||||
}
|
||||
|
||||
void test(Stream<String> stream, Optional<String> opt) {
|
||||
stream.filter(<warning descr="Condition 'String.class::isInstance' is redundant and can be replaced with '!= null'">String.class::isInstance</warning>).forEach(System.out::println);
|
||||
opt.filter(<warning descr="Method reference result is always 'true'">String.class::isInstance</warning>).ifPresent(System.out::println);
|
||||
}
|
||||
|
||||
// IDEA-152871
|
||||
static class A {
|
||||
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2000-2017 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.codeInsight.daemon.quickFix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspection;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class RedundantInstanceofFixTest extends LightQuickFixParameterizedTestCase {
|
||||
@NotNull
|
||||
@Override
|
||||
protected LocalInspectionTool[] configureLocalInspectionTools() {
|
||||
return new LocalInspectionTool[]{new DataFlowInspection()};
|
||||
}
|
||||
|
||||
public void test() {
|
||||
doAllTests();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return LightCodeInsightFixtureTestCase.JAVA_8;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/codeInsight/daemonCodeAnalyzer/quickFix/redundantInstanceOf";
|
||||
}
|
||||
}
|
||||
+1
@@ -59,6 +59,7 @@ public class DataFlowInspectionTestSuite {
|
||||
suite.addTestSuite(ReplaceWithTrivialLambdaFixTest.class);
|
||||
suite.addTestSuite(UnwrapIfStatementFixTest.class);
|
||||
suite.addTestSuite(StreamFilterNotNullFixTest.class);
|
||||
suite.addTestSuite(RedundantInstanceofFixTest.class);
|
||||
return suite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ inspection.data.flow.turn.off.true.asserts.quickfix=Don't report always true ass
|
||||
inspection.data.flow.turn.off.constant.references.quickfix=Don't report values which are guaranteed to be constant
|
||||
inspection.data.flow.turn.off.nullable.returning.notnull.quickfix=Don't report nullable methods which always return not-null value
|
||||
inspection.data.flow.turn.off.unchecked.optional.get.quickfix=Don't report Optional.get() calls without previous isPresent check
|
||||
inspection.data.flow.redundant.instanceof.quickfix=Replace with != null
|
||||
inspection.data.flow.redundant.instanceof.quickfix=Replace with a null check
|
||||
inspection.data.flow.simplify.boolean.expression.quickfix=Simplify boolean expression
|
||||
inspection.data.flow.simplify.to.assignment.quickfix.name=Simplify to normal assignment
|
||||
inspection.data.flow.filter.notnull.quickfix=Insert 'filter(Objects::nonNull)' step
|
||||
|
||||
Reference in New Issue
Block a user