lambda -> method refs: check if result method reference is valid

This commit is contained in:
Anna Kozlova
2014-11-11 15:21:35 +01:00
parent 01c823a6d8
commit 0506cb77bc
5 changed files with 115 additions and 132 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -0,0 +1,13 @@
// "Replace lambda with method reference" "true"
class Example {
public void m() {
}
{
Runnable r = ex()::m;
}
Example ex() {
return this;
}
}

View File

@@ -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;}
}

View File

@@ -0,0 +1,13 @@
// "Replace lambda with method reference" "true"
class Example {
public void m() {
}
{
Runnable r = () -> ex().<caret>m();
}
Example ex() {
return this;
}
}