StreamChainInliner: special support for toArray; support for toList/toArray expected component type nullability

Fixes IDEA-194697 Nullability analysis for stream chains should check against element-level nullability annotations
This commit is contained in:
Tagir Valeev
2018-06-29 10:23:58 +07:00
parent 85c58af2c6
commit d8566eaf48
4 changed files with 76 additions and 1 deletions
@@ -16,6 +16,7 @@
package com.intellij.codeInspection.dataFlow.inliner;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaUnknownValue;
@@ -27,6 +28,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpectedTypeUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.psiutils.StreamApiUtil;
import org.jetbrains.annotations.NotNull;
@@ -49,6 +51,9 @@ public class StreamChainInliner implements CallInliner {
private static final CallMatcher MATCH_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch",
"noneMatch").parameterCount(1);
private static final CallMatcher SUM_TERMINAL = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "sum", "count").parameterCount(0);
private static final CallMatcher TO_ARRAY_TERMINAL = anyOf(
instanceCall(JAVA_UTIL_STREAM_STREAM, "toArray").parameterCount(0),
instanceCall(JAVA_UTIL_STREAM_STREAM, "toArray").parameterTypes("java.util.function.IntFunction"));
private static final CallMatcher OPTIONAL_TERMINAL =
anyOf(instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "min", "max").parameterCount(0),
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "reduce").parameterCount(1),
@@ -117,6 +122,7 @@ public class StreamChainInliner implements CallInliner {
.register(SUM_TERMINAL, SumTerminalStep::new)
.register(MIN_MAX_TERMINAL, MinMaxTerminalStep::new)
.register(OPTIONAL_TERMINAL, OptionalTerminalStep::new)
.register(TO_ARRAY_TERMINAL, ToArrayStep::new)
.register(COLLECT_TERMINAL, StreamChainInliner::createTerminalFromCollector);
private static final Step NULL_TERMINAL_STEP = new Step(null, null, null) {
@@ -558,7 +564,25 @@ public class StreamChainInliner implements CallInliner {
@Override
boolean expectNotNull() {
return myImmutable;
if (myImmutable) return true;
PsiType collectionType = ExpectedTypeUtils.findExpectedType(myCall, false);
PsiType itemType = JavaGenericsUtil.getCollectionItemType(collectionType, myCall.getResolveScope());
return DfaPsiUtil.getTypeNullability(itemType) == Nullability.NOT_NULL;
}
}
static class ToArrayStep extends ToCollectionStep {
ToArrayStep(@NotNull PsiMethodCallExpression call) {
super(call, ArrayUtil.getFirstElement(call.getArgumentList().getExpressions()), false);
}
@Override
protected void pushInitialValue(CFGBuilder builder) {
if (myFunction == null) {
super.pushInitialValue(builder);
} else {
builder.pushUnknown().invokeFunction(1, myFunction, Nullability.NOT_NULL);
}
}
}
@@ -235,4 +235,7 @@ public class StreamInlining {
}
}
void testToArray(List<String> list) {
list.stream().toArray(size -> <warning descr="Function may return null, but it's not allowed here">null</warning>);
}
}
@@ -0,0 +1,44 @@
import foo.NotNull;
import foo.Nullable;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class StreamTypeAnnoInlining {
void testToArray() {
@NotNull Object @NotNull[] foo0 = Stream.of("a", "b")
.map(x-> <warning descr="Function may return null, but it's not allowed here">"a".equals(x) ? null : x.toUpperCase()</warning>)
.toArray();
// IDEA-194697
@NotNull String @NotNull[] foo = Stream.of("a", "b")
.map(x-> <warning descr="Function may return null, but it's not allowed here">"a".equals(x) ? null : x.toUpperCase()</warning>)
.toArray(String[]::new);
@NotNull String @NotNull[] foo1 = Stream.of("b", "c")
.map(x-> <warning descr="Condition '\"a\".equals(x)' is always 'false'">"a".equals(x)</warning> ? null : x.toUpperCase())
.toArray(String[]::new);
@NotNull String @NotNull[] foo2 = Stream.of("a", "b")
.map(x-> "a".equals(x) ? null : x.toUpperCase())
.filter(Objects::nonNull)
.toArray(String[]::new);
}
void testFromList(List<@NotNull String> list) {
list.stream().filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
}
List<@NotNull String> testFromNullableToNotNull(List<@Nullable String> list) {
return list.stream().map(x -> <warning descr="Function may return null, but it's not allowed here">x</warning>).collect(Collectors.toList());
}
List<@Nullable String> testFromNullableToNullable(List<@Nullable String> list) {
return list.stream().map(x -> x).collect(Collectors.toList());
}
List<@NotNull String> testFromUnknownToNotNull(List<String> list) {
return list.stream().map(x -> x).collect(Collectors.toList());
}
}
@@ -197,6 +197,10 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testStreamCollectorInlining() { doTest(); }
public void testStreamComparatorInlining() { doTest(); }
public void testStreamKnownSource() { doTest(); }
public void testStreamTypeAnnoInlining() {
setupTypeUseAnnotations("foo", myFixture);
doTest();
}
public void testMapGetWithNotNullKeys() { doTestWithCustomAnnotations(); }
public void testInferNestedForeachNullability() { doTestWithCustomAnnotations(); }