[java-inspections] IDEA-278390 SuspiciousToArrayCall inspection: better recognizing of Stream API semantics

GitOrigin-RevId: 334190f498fef1624c6d58719bd698a0352340a5
This commit is contained in:
Tagir Valeev
2021-09-22 08:47:09 +00:00
committed by intellij-monorepo-bot
parent 0ba7cf9228
commit 30fb27b314
2 changed files with 66 additions and 11 deletions
@@ -28,10 +28,8 @@ import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.FunctionalExpressionUtils;
import com.siyeh.ig.psiutils.StreamApiUtil;
import com.siyeh.ig.psiutils.TypeUtils;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.*;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -77,10 +75,8 @@ public class SuspiciousToArrayCallInspection extends BaseInspection {
if (qualifierExpression == null) {
return;
}
final PsiType type = qualifierExpression.getType();
if (!(type instanceof PsiClassType)) {
return;
}
final PsiClassType classType = ObjectUtils.tryCast(qualifierExpression.getType(), PsiClassType.class);
if (classType == null || classType.isRaw()) return;
final PsiExpressionList argumentList = expression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length != 1) {
@@ -89,8 +85,6 @@ public class SuspiciousToArrayCallInspection extends BaseInspection {
final PsiExpression argument = PsiUtil.skipParenthesizedExprDown(arguments[0]);
if (argument == null) return;
final PsiClassType classType = (PsiClassType)type;
if (classType.isRaw()) return;
final PsiClass aClass = classType.resolve();
if (aClass == null) {
return;
@@ -106,11 +100,52 @@ public class SuspiciousToArrayCallInspection extends BaseInspection {
else if (InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_STREAM)) {
PsiType argumentType = getIntFunctionParameterType(argument);
if (argumentType != null) {
checkArrayTypes(argument, expression, argumentType, StreamApiUtil.getStreamElementType(classType, false));
checkArrayTypes(argument, expression, argumentType, getStreamElementType(qualifierExpression));
}
}
}
private static final CallMatcher STREAM_FILTER = CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "filter")
.parameterTypes(CommonClassNames.JAVA_UTIL_FUNCTION_PREDICATE);
private static final CallMatcher CLASS_INSTANCEOF = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_CLASS, "isInstance")
.parameterCount(1);
/**
* @param expression stream expression
* @return type of elements inside the stream. Tries to take into account previous filters by element type
*/
private static @Nullable PsiType getStreamElementType(PsiExpression expression) {
PsiMethodCallExpression call =
ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiMethodCallExpression.class);
while (STREAM_FILTER.test(call)) {
PsiExpression predicate = PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]);
if (predicate instanceof PsiMethodReferenceExpression) {
if (CLASS_INSTANCEOF.methodReferenceMatches((PsiMethodReferenceExpression)predicate)) {
PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(((PsiMethodReferenceExpression)predicate).getQualifierExpression());
if (qualifier instanceof PsiClassObjectAccessExpression) {
return ((PsiClassObjectAccessExpression)qualifier).getOperand().getType();
}
}
}
else if (predicate instanceof PsiLambdaExpression) {
PsiParameter[] parameters = ((PsiLambdaExpression)predicate).getParameterList().getParameters();
if (parameters.length == 1) {
PsiExpression lambdaBody =
PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(((PsiLambdaExpression)predicate).getBody()));
if (lambdaBody instanceof PsiInstanceOfExpression &&
ExpressionUtils.isReferenceTo(((PsiInstanceOfExpression)lambdaBody).getOperand(), parameters[0])) {
PsiTypeElement checkType = ((PsiInstanceOfExpression)lambdaBody).getCheckType();
if (checkType != null) {
return checkType.getType();
}
}
}
}
call = MethodCallUtils.getQualifierMethodCall(call);
}
return StreamApiUtil.getStreamElementType(expression.getType(), false);
}
private static PsiType getIntFunctionParameterType(PsiExpression argument) {
PsiType argumentType = FunctionalExpressionUtils.getFunctionalExpressionType(argument);
return PsiUtil.substituteTypeParameter(argumentType, "java.util.function.IntFunction", 0, false);
@@ -136,6 +136,26 @@ public class SuspiciousToArrayCallInspectionTest extends LightJavaInspectionTest
"class Bar extends Foo {}");
}
public void testStreamFilter() {
doTest("import java.util.Arrays;\n" +
"class Parent {}\n" +
"class Child extends Parent {}\n" +
"class Test {\n" +
" void test(Parent[] parent) {\n" +
" Child[] children = Arrays.stream(parent)\n" +
" .filter(t -> t instanceof Child)\n" +
" .toArray(Child[]::new);\n" +
" Child[] children2 = Arrays.stream(parent)\n" +
" .filter(Child.class::isInstance)\n" +
" .toArray(Child[]::new);\n" +
" Child[] children3 = Arrays.stream(parent)\n" +
" .filter(Child.class::isInstance)\n" +
" .filter(c -> c.hashCode() > 0)\n" +
" .toArray(Child[]::new);\n" +
" }\n" +
"}");
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {