mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
lambda -> method refs: check if result method reference is valid
This commit is contained in:
+68
-128
@@ -19,21 +19,20 @@ import com.intellij.codeInsight.FileModificationService;
|
||||
import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* User: anna
|
||||
*/
|
||||
@@ -92,138 +91,70 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
|
||||
protected static PsiCallExpression canBeMethodReferenceProblem(@Nullable final PsiElement body,
|
||||
final PsiParameter[] parameters,
|
||||
PsiType functionalInterfaceType) {
|
||||
PsiCallExpression methodCall = extractMethodCallFromBlock(body);
|
||||
final PsiCallExpression callExpression = extractMethodCallFromBlock(body);
|
||||
if (callExpression instanceof PsiNewExpression && ((PsiNewExpression)callExpression).getAnonymousClass() != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (methodCall instanceof PsiNewExpression && ((PsiNewExpression)methodCall).getAnonymousClass() != null) return null;
|
||||
|
||||
if (methodCall != null) {
|
||||
final PsiExpressionList argumentList = methodCall.getArgumentList();
|
||||
if (argumentList != null) {
|
||||
final PsiExpression[] expressions = argumentList.getExpressions();
|
||||
|
||||
PsiMethod psiMethod = methodCall.resolveMethod();
|
||||
PsiClass containingClass;
|
||||
boolean isConstructor;
|
||||
if (psiMethod == null) {
|
||||
isConstructor = true;
|
||||
if (!(methodCall instanceof PsiNewExpression)) return null;
|
||||
final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)methodCall).getClassOrAnonymousClassReference();
|
||||
if (classReference == null) return null;
|
||||
containingClass = (PsiClass)classReference.resolve();
|
||||
}
|
||||
else {
|
||||
containingClass = psiMethod.getContainingClass();
|
||||
isConstructor = psiMethod.isConstructor();
|
||||
}
|
||||
boolean isReceiverType = PsiMethodReferenceUtil.isReceiverType(functionalInterfaceType, containingClass, psiMethod);
|
||||
if (psiMethod != null && !isConstructor) {
|
||||
PsiMethod nonAmbiguousMethod = ensureNonAmbiguousMethod(parameters, psiMethod, isReceiverType);
|
||||
if (nonAmbiguousMethod == null) return null;
|
||||
psiMethod = nonAmbiguousMethod;
|
||||
containingClass = nonAmbiguousMethod.getContainingClass();
|
||||
}
|
||||
if (containingClass == null) return null;
|
||||
final boolean staticOrValidConstructorRef;
|
||||
if (isConstructor) {
|
||||
staticOrValidConstructorRef =
|
||||
(containingClass.getContainingClass() == null || containingClass.hasModifierProperty(PsiModifier.STATIC));
|
||||
}
|
||||
else {
|
||||
staticOrValidConstructorRef = psiMethod.hasModifierProperty(PsiModifier.STATIC);
|
||||
}
|
||||
|
||||
final int offset = isReceiverType && !staticOrValidConstructorRef ? 1 : 0;
|
||||
if (parameters.length != expressions.length + offset) return null;
|
||||
|
||||
for (int i = 0; i < expressions.length; i++) {
|
||||
PsiExpression psiExpression = expressions[i];
|
||||
if (!(psiExpression instanceof PsiReferenceExpression)) return null;
|
||||
final PsiElement resolve = ((PsiReferenceExpression)psiExpression).resolve();
|
||||
if (resolve == null) return null;
|
||||
if (parameters[i + offset] != resolve) return null;
|
||||
}
|
||||
|
||||
final PsiExpression qualifierExpression;
|
||||
if (methodCall instanceof PsiMethodCallExpression) {
|
||||
qualifierExpression = ((PsiMethodCallExpression)methodCall).getMethodExpression().getQualifierExpression();
|
||||
}
|
||||
else if (methodCall instanceof PsiNewExpression) {
|
||||
qualifierExpression = ((PsiNewExpression)methodCall).getQualifier();
|
||||
}
|
||||
else {
|
||||
qualifierExpression = null;
|
||||
}
|
||||
if (offset > 0) {
|
||||
if (!(qualifierExpression instanceof PsiReferenceExpression) ||
|
||||
((PsiReferenceExpression)qualifierExpression).resolve() != parameters[0]) {
|
||||
final String methodReferenceText = createMethodReferenceText(callExpression, functionalInterfaceType, parameters);
|
||||
if (methodReferenceText != null) {
|
||||
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(callExpression.getProject());
|
||||
final PsiMethodReferenceExpression methodReferenceExpression =
|
||||
(PsiMethodReferenceExpression)elementFactory.createExpressionFromText(methodReferenceText, callExpression);
|
||||
final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
|
||||
try {
|
||||
map.put(methodReferenceExpression, functionalInterfaceType);
|
||||
final JavaResolveResult result = methodReferenceExpression.advancedResolve(false);
|
||||
final PsiElement element = result.getElement();
|
||||
if (element != null && result.isAccessible()) {
|
||||
if (element instanceof PsiMethod && !isSimpleCall(parameters, callExpression, (PsiMethod)element)) {
|
||||
return null;
|
||||
}
|
||||
return callExpression;
|
||||
}
|
||||
else if (qualifierExpression != null) {
|
||||
final Ref<Boolean> usedInQualifier = new Ref<Boolean>(false);
|
||||
qualifierExpression.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
final PsiElement resolve = expression.resolve();
|
||||
if (resolve instanceof PsiParameter && ArrayUtilRt.find(parameters, resolve) > -1) {
|
||||
usedInQualifier.set(true);
|
||||
return;
|
||||
}
|
||||
super.visitReferenceExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(PsiNewExpression expression) {
|
||||
usedInQualifier.set(true);
|
||||
super.visitNewExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
|
||||
usedInQualifier.set(true);
|
||||
super.visitMethodCallExpression(expression);
|
||||
}
|
||||
});
|
||||
if (usedInQualifier.get()) return null;
|
||||
} else if (containingClass != PsiTreeUtil.getParentOfType(body, PsiClass.class) && containingClass.getName() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isReceiverType && psiMethod != null) {
|
||||
final PsiClass memberClass = psiMethod.getContainingClass();
|
||||
PsiClass accessClass;
|
||||
if (qualifierExpression != null) {
|
||||
accessClass = PsiUtil.resolveClassInType(qualifierExpression.getType());
|
||||
}
|
||||
else {
|
||||
accessClass = PsiTreeUtil.getParentOfType(methodCall, PsiClass.class);
|
||||
while (accessClass != null && !InheritanceUtil.isInheritorOrSelf(accessClass, memberClass, true)) {
|
||||
accessClass = PsiTreeUtil.getParentOfType(accessClass, PsiClass.class, true);
|
||||
}
|
||||
}
|
||||
if (!JavaResolveUtil.isAccessible(psiMethod, memberClass, psiMethod.getModifierList(),
|
||||
methodCall, accessClass, null, methodCall.getContainingFile())) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return methodCall;
|
||||
} else if (methodCall instanceof PsiNewExpression) {
|
||||
final PsiExpression[] dimensions = ((PsiNewExpression)methodCall).getArrayDimensions();
|
||||
if (dimensions.length > 0) {
|
||||
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
if (interfaceMethod != null) {
|
||||
final PsiParameter[] psiParameters = interfaceMethod.getParameterList().getParameters();
|
||||
if (psiParameters.length == 1 && PsiType.INT.equals(psiParameters[0].getType())) {
|
||||
return methodCall;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
map.remove(methodReferenceExpression);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSimpleCall(PsiParameter[] parameters, PsiCallExpression callExpression, PsiMethod psiMethod) {
|
||||
final PsiExpressionList argumentList = callExpression.getArgumentList();
|
||||
if (argumentList == null) {
|
||||
return false;
|
||||
}
|
||||
int offset = parameters.length - psiMethod.getParameterList().getParametersCount();
|
||||
final PsiExpression[] expressions = argumentList.getExpressions();
|
||||
for (int i = 0; i < expressions.length; i++) {
|
||||
if (!resolvesToParameter(expressions[i], parameters[i + offset])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (offset > 0) {
|
||||
final PsiExpression qualifier;
|
||||
if (callExpression instanceof PsiMethodCallExpression) {
|
||||
qualifier = ((PsiMethodCallExpression)callExpression).getMethodExpression().getQualifierExpression();
|
||||
}
|
||||
else if (callExpression instanceof PsiNewExpression) {
|
||||
qualifier = ((PsiNewExpression)callExpression).getQualifier();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!resolvesToParameter(qualifier, parameters[0])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean resolvesToParameter(PsiExpression expression, PsiParameter parameter) {
|
||||
return expression instanceof PsiReferenceExpression && ((PsiReferenceExpression)expression).resolve() == parameter;
|
||||
}
|
||||
|
||||
public static PsiCallExpression extractMethodCallFromBlock(PsiElement body) {
|
||||
PsiCallExpression methodCall = null;
|
||||
if (body instanceof PsiCallExpression) {
|
||||
@@ -303,6 +234,9 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
|
||||
boolean isReceiverType = PsiMethodReferenceUtil.isReceiverType(functionalInterfaceType, containingClass, psiMethod);
|
||||
final String qualifier = isReceiverType ? composeReceiverQualifierText(parameters, psiMethod, containingClass, qualifierExpression)
|
||||
: qualifierExpression.getText();
|
||||
if (qualifier == null) {
|
||||
return null;
|
||||
}
|
||||
methodRefText = qualifier + "::" + ((PsiMethodCallExpression)element).getTypeArgumentList().getText() + methodReferenceName;
|
||||
}
|
||||
else {
|
||||
@@ -315,7 +249,11 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
|
||||
treeContainingClass = PsiTreeUtil.getParentOfType(treeContainingClass, PsiClass.class, true);
|
||||
}
|
||||
if (treeContainingClass != null && containingClass != parentContainingClass && treeContainingClass != parentContainingClass) {
|
||||
methodRefText = treeContainingClass.getName() + ".this";
|
||||
final String treeContainingClassName = treeContainingClass.getName();
|
||||
if (treeContainingClassName == null) {
|
||||
return null;
|
||||
}
|
||||
methodRefText = treeContainingClassName + ".this";
|
||||
} else {
|
||||
methodRefText = "this";
|
||||
}
|
||||
@@ -368,7 +306,9 @@ public class LambdaCanBeMethodReferenceInspection extends BaseJavaBatchLocalInsp
|
||||
PsiClass containingClass,
|
||||
@NotNull PsiExpression qualifierExpression) {
|
||||
final PsiMethod nonAmbiguousMethod = ensureNonAmbiguousMethod(parameters, psiMethod, true);
|
||||
LOG.assertTrue(nonAmbiguousMethod != null);
|
||||
if (nonAmbiguousMethod == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiClass nonAmbiguousContainingClass = nonAmbiguousMethod.getContainingClass();
|
||||
if (!containingClass.equals(nonAmbiguousContainingClass)) {
|
||||
return getClassReferenceName(nonAmbiguousContainingClass);
|
||||
|
||||
+9
-4
@@ -227,7 +227,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
//method reference
|
||||
final PsiCallExpression callExpression = LambdaCanBeMethodReferenceInspection
|
||||
.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body,
|
||||
new PsiParameter[]{parameter}, null);
|
||||
new PsiParameter[]{parameter},
|
||||
createDefaultConsumerType(parameter.getProject(), parameter));
|
||||
if (callExpression == null) {
|
||||
return true;
|
||||
}
|
||||
@@ -291,9 +292,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
String methodReferenceText = null;
|
||||
final PsiCallExpression callExpression = LambdaCanBeMethodReferenceInspection.extractMethodCallFromBlock(body);
|
||||
if (callExpression != null) {
|
||||
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
|
||||
final PsiClass consumerClass = psiFacade.findClass("java.util.function.Consumer", GlobalSearchScope.allScope(project));
|
||||
final PsiClassType functionalType = consumerClass != null ? psiFacade.getElementFactory().createType(consumerClass, callExpression.getType()) : null;
|
||||
final PsiClassType functionalType = createDefaultConsumerType(project, parameter);
|
||||
|
||||
final PsiCallExpression toConvertCall = LambdaCanBeMethodReferenceInspection.canBeMethodReferenceProblem(body instanceof PsiBlockStatement ? ((PsiBlockStatement)body).getCodeBlock() : body, parameters, functionalType);
|
||||
methodReferenceText = LambdaCanBeMethodReferenceInspection.createMethodReferenceText(toConvertCall, functionalType, parameters);
|
||||
@@ -330,6 +329,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiClassType createDefaultConsumerType(Project project, PsiParameter parameter) {
|
||||
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
|
||||
final PsiClass consumerClass = psiFacade.findClass("java.util.function.Consumer", GlobalSearchScope.allScope(project));
|
||||
return consumerClass != null ? psiFacade.getElementFactory().createType(consumerClass, parameter.getType()) : null;
|
||||
}
|
||||
|
||||
private static class ReplaceWithCollectCallFix implements LocalQuickFix {
|
||||
@NotNull
|
||||
@Override
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace lambda with method reference" "true"
|
||||
class Example {
|
||||
public void m() {
|
||||
}
|
||||
|
||||
{
|
||||
Runnable r = ex()::m;
|
||||
}
|
||||
|
||||
Example ex() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// "Replace lambda with method reference" "false"
|
||||
import java.io.PrintStream;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
class Test {
|
||||
{
|
||||
BiConsumer<PrintStream, String> printer = (printStream, x) -> get(printSt<caret>ream).println(x);
|
||||
}
|
||||
|
||||
PrintStream get(PrintStream p) {return p;}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Replace lambda with method reference" "true"
|
||||
class Example {
|
||||
public void m() {
|
||||
}
|
||||
|
||||
{
|
||||
Runnable r = () -> ex().<caret>m();
|
||||
}
|
||||
|
||||
Example ex() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user