functional interface suggester: accept non exact method references

This commit is contained in:
Anna Kozlova
2014-12-11 17:20:11 +01:00
parent 3265f05164
commit e767382757
5 changed files with 103 additions and 22 deletions
@@ -33,11 +33,13 @@ import java.util.*;
public class FunctionalInterfaceSuggester {
public static Collection<? extends PsiType> suggestFunctionalInterfaces(final @NotNull PsiFunctionalExpression expression) {
final PsiType qualifierType = expression instanceof PsiMethodReferenceExpression
? PsiMethodReferenceUtil.getQualifierType((PsiMethodReferenceExpression)expression) : null;
return suggestFunctionalInterfaces(expression, new NullableFunction<PsiClass, PsiType>() {
@Nullable
@Override
public PsiType fun(PsiClass aClass) {
return composeAcceptableType(aClass, expression);
return composeAcceptableType(aClass, expression, qualifierType);
}
});
}
@@ -101,8 +103,7 @@ public class FunctionalInterfaceSuggester {
}
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(aClass.getProject());
final PsiType type = elementFactory.createType(aClass, substitutor);
return type;
return elementFactory.createType(aClass, substitutor);
}
return null;
}
@@ -136,48 +137,52 @@ public class FunctionalInterfaceSuggester {
return typesToSuggest;
}
private static PsiType composeAcceptableType(@NotNull PsiClass interface2Consider, @NotNull PsiFunctionalExpression expression) {
private static PsiType composeAcceptableType(@NotNull PsiClass interface2Consider,
@NotNull PsiFunctionalExpression expression,
PsiType qualifierType) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(interface2Consider.getProject());
final PsiType type = elementFactory.createType(interface2Consider, PsiSubstitutor.EMPTY);
if (expression.isAcceptable(type)) {
return type;
}
return composeAcceptableType(interface2Consider, expression, elementFactory);
return composeAcceptableType(interface2Consider, expression, qualifierType, elementFactory);
}
private static PsiType composeAcceptableType(final PsiClass interface2Consider,
final PsiFunctionalExpression expression,
final PsiElementFactory elementFactory) {
PsiType qualifierType, final PsiElementFactory elementFactory) {
if (interface2Consider.hasTypeParameters()) {
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(interface2Consider);
if (interfaceMethod != null) {
final PsiParameter[] parameters = interfaceMethod.getParameterList().getParameters();
final PsiParameter[] functionalExprParameters;
PsiParameter[] functionalExprParameters;
int offset = 0;
final PsiType[] left = new PsiType[parameters.length];
final PsiType[] right = new PsiType[parameters.length];
if (expression instanceof PsiLambdaExpression && ((PsiLambdaExpression)expression).hasFormalParameterTypes()) {
functionalExprParameters = ((PsiLambdaExpression)expression).getParameterList().getParameters();
}
else if (expression instanceof PsiMethodReferenceExpression && ((PsiMethodReferenceExpression)expression).isExact()) {
final PsiElement exactMethod = ((PsiMethodReferenceExpression)expression).resolve();
if (!(exactMethod instanceof PsiMethod)) {
if (parameters.length != functionalExprParameters.length) {
return null;
}
functionalExprParameters = ((PsiMethod)exactMethod).getParameterList().getParameters();
}
else if (expression instanceof PsiMethodReferenceExpression) {
final PsiMethod method = getTargetMethod((PsiMethodReferenceExpression)expression, qualifierType, parameters, left, right);
if (method == null) {
return null;
}
functionalExprParameters = method.getParameterList().getParameters();
if (PsiMethodReferenceUtil.isStaticallyReferenced((PsiMethodReferenceExpression)expression) && !method.hasModifierProperty(PsiModifier.STATIC)) {
offset = 1;
}
} else {
return null;
}
if (parameters.length != functionalExprParameters.length) {
return null;
}
final PsiType[] left = new PsiType[parameters.length];
final PsiType[] right = new PsiType[parameters.length];
for (int i = 0; i < parameters.length; i++) {
left[i] = parameters[i].getType();
right[i] = functionalExprParameters[i].getType();
for (int i = 0; i < functionalExprParameters.length; i++) {
left [i + offset] = parameters[i + offset].getType();
right[i + offset] = functionalExprParameters[i].getType();
}
final PsiSubstitutor substitutor = PsiResolveHelper.SERVICE.getInstance(interface2Consider.getProject())
@@ -192,4 +197,33 @@ public class FunctionalInterfaceSuggester {
}
return null;
}
@Nullable
private static PsiMethod getTargetMethod(PsiMethodReferenceExpression expression,
PsiType qualifierType,
PsiParameter[] parameters,
PsiType[] left,
PsiType[] right) {
final boolean staticallyReferenced = PsiMethodReferenceUtil.isStaticallyReferenced(expression);
final JavaResolveResult[] results = expression.multiResolve(true);
for (JavaResolveResult result : results) {
final PsiElement element = result.getElement();
if (element instanceof PsiMethod) {
int offset = staticallyReferenced && !((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC) ? 1 : 0;
final PsiParameter[] functionalExprParameters = ((PsiMethod)element).getParameterList().getParameters();
if (functionalExprParameters.length + offset == parameters.length) {
if (offset > 0) {
if (qualifierType == null) {
continue;
}
left[0] = parameters[0].getType();
right[0] = qualifierType;
}
return (PsiMethod)element;
}
}
}
return null;
}
}
@@ -95,6 +95,29 @@ public class PsiMethodReferenceUtil {
return !varargs || parameterTypes.length - 1 <= argTypes.length - offset;
}
@Nullable
public static PsiType getQualifierType(PsiMethodReferenceExpression expression) {
PsiType qualifierType = null;
final PsiTypeElement typeElement = expression.getQualifierType();
if (typeElement != null) {
qualifierType = typeElement.getType();
} else {
final PsiElement qualifier = expression.getQualifier();
if (qualifier instanceof PsiExpression) {
qualifierType = ((PsiExpression)qualifier).getType();
}
}
if (qualifierType == null) {
final QualifierResolveResult qualifierResolveResult = getQualifierResolveResult(expression);
final PsiClass containingClass = qualifierResolveResult.getContainingClass();
if (containingClass == null) {
return null;
}
qualifierType = JavaPsiFacade.getElementFactory(expression.getProject()).createType(containingClass);
}
return qualifierType;
}
public static class QualifierResolveResult {
private final PsiClass myContainingClass;
private final PsiSubstitutor mySubstitutor;
@@ -0,0 +1,10 @@
@FunctionalInterface
interface I<T> {
void foo(T t);
}
class Foo {
void test() {
I<String> l = String::toLowerCase;
}
}
@@ -0,0 +1,10 @@
@FunctionalInterface
interface I<T> {
void foo(T t);
}
class Foo {
void test() {
<selection>String::toLowerCase</selection>;
}
}
@@ -445,6 +445,10 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase {
doTest(new MockIntroduceVariableHandler("l", false, false, false, "java.util.function.Consumer<java.lang.Integer>", true));
}
public void testMethodRefNotInContextInferredNonExact() {
doTest(new MockIntroduceVariableHandler("l", false, false, false, "I<java.lang.String>", true));
}
public void testMethodRefNotInContextInferredFilterWithNonAcceptableSince() {
//though test extracts method reference which is not suppose to appear with language level 1.7
//@since 1.8 in Consumer prevent it to appear at first position