method references: remove code duplication in return type checks

This commit is contained in:
Anna Kozlova
2016-01-29 21:18:43 +03:00
parent 4838874a41
commit 70e66b80fb
6 changed files with 93 additions and 92 deletions

View File

@@ -16,6 +16,7 @@
package com.intellij.psi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -77,6 +78,90 @@ public class PsiMethodReferenceUtil {
return qualifierType;
}
public static boolean isReturnTypeCompatible(PsiMethodReferenceExpression expression,
JavaResolveResult result,
PsiType functionalInterfaceType) {
return isReturnTypeCompatible(expression, result, functionalInterfaceType, null);
}
private static boolean isReturnTypeCompatible(PsiMethodReferenceExpression expression,
JavaResolveResult result,
PsiType functionalInterfaceType,
Ref<String> errorMessage) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
if (interfaceMethod != null) {
final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
if (PsiType.VOID.equals(interfaceReturnType) || interfaceReturnType == null) {
return true;
}
PsiSubstitutor subst = result.getSubstitutor();
PsiType methodReturnType = null;
PsiClass containingClass = null;
final PsiElement resolve = result.getElement();
if (resolve instanceof PsiMethod) {
containingClass = ((PsiMethod)resolve).getContainingClass();
PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(expression, expression, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(expression));
if (returnType == null) {
returnType = ((PsiMethod)resolve).getReturnType();
if (PsiType.VOID.equals(returnType)) {
return false;
}
PsiClass qContainingClass = getQualifierResolveResult(expression).getContainingClass();
if (qContainingClass != null && containingClass != null &&
isReceiverType(getFirstParameterType(functionalInterfaceType, expression), qContainingClass, subst)) {
subst = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, subst);
LOG.assertTrue(subst != null);
}
methodReturnType = subst.substitute(returnType);
if (methodReturnType != null) {
methodReturnType = PsiUtil.captureToplevelWildcards(methodReturnType, expression);
}
}
else {
methodReturnType = returnType;
}
}
else if (resolve instanceof PsiClass) {
if (resolve == JavaPsiFacade.getElementFactory(expression.getProject()).getArrayClass(PsiUtil.getLanguageLevel(resolve))) {
final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters();
if (typeParameters.length == 1) {
final PsiType arrayComponentType = subst.substitute(typeParameters[0]);
if (arrayComponentType == null) {
return false;
}
methodReturnType = arrayComponentType.createArrayType();
}
}
containingClass = (PsiClass)resolve;
}
if (methodReturnType == null) {
if (containingClass == null) {
return false;
}
methodReturnType = JavaPsiFacade.getElementFactory(expression.getProject()).createType(containingClass, subst);
}
if (TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType)) {
return true;
}
if (errorMessage != null) {
errorMessage.set("Bad return type in method reference: " +
"cannot convert " + methodReturnType.getCanonicalText() + " to " + interfaceReturnType.getCanonicalText());
}
}
return false;
}
public static class QualifierResolveResult {
private final PsiClass myContainingClass;
private final PsiSubstitutor mySubstitutor;
@@ -284,35 +369,9 @@ public class PsiMethodReferenceUtil {
}
public static String checkReturnType(PsiMethodReferenceExpression expression, JavaResolveResult result, PsiType functionalInterfaceType) {
final PsiElement resolve = result.getElement();
if (resolve instanceof PsiMethod) {
final PsiClass containingClass = ((PsiMethod)resolve).getContainingClass();
LOG.assertTrue(containingClass != null);
PsiSubstitutor subst = result.getSubstitutor();
PsiClass qContainingClass = getQualifierResolveResult(expression).getContainingClass();
if (qContainingClass != null && isReceiverType(getFirstParameterType(functionalInterfaceType, expression), qContainingClass, subst)) {
subst = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, subst);
LOG.assertTrue(subst != null);
}
final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(expression, expression,
(PsiMethod)resolve, null,
PsiUtil.getLanguageLevel(expression));
if (returnType == null) {
returnType = ((PsiMethod)resolve).getReturnType();
}
PsiType methodReturnType = subst.substitute(returnType);
if (interfaceReturnType != null && !PsiType.VOID.equals(interfaceReturnType)) {
if (methodReturnType == null) {
methodReturnType = JavaPsiFacade.getElementFactory(expression.getProject()).createType(containingClass, subst);
}
if (!TypeConversionUtil.isAssignable(interfaceReturnType, PsiUtil.captureToplevelWildcards(methodReturnType, expression))) {
return "Bad return type in method reference: cannot convert " + methodReturnType.getCanonicalText() + " to " + interfaceReturnType.getCanonicalText();
}
}
final Ref<String> errorMessage = Ref.create();
if (!isReturnTypeCompatible(expression, result, functionalInterfaceType, errorMessage)) {
return errorMessage.get();
}
return null;
}

View File

@@ -459,65 +459,7 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
return false;
}
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(left);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
if (interfaceMethod != null) {
final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(left);
if (PsiType.VOID.equals(interfaceReturnType) || interfaceReturnType == null) {
return true;
}
PsiSubstitutor subst = result.getSubstitutor();
PsiType methodReturnType = null;
PsiClass containingClass = null;
if (resolve instanceof PsiMethod) {
containingClass = ((PsiMethod)resolve).getContainingClass();
PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(this, this, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(this));
if (returnType == null) {
returnType = ((PsiMethod)resolve).getReturnType();
}
if (PsiType.VOID.equals(returnType)) {
return false;
}
PsiClass qContainingClass = PsiMethodReferenceUtil.getQualifierResolveResult(this).getContainingClass();
if (qContainingClass != null && containingClass != null &&
PsiMethodReferenceUtil.isReceiverType(PsiMethodReferenceUtil.getFirstParameterType(left, this), qContainingClass, subst)) {
subst = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, subst);
LOG.assertTrue(subst != null);
}
methodReturnType = subst.substitute(returnType);
}
else if (resolve instanceof PsiClass) {
if (resolve == JavaPsiFacade.getElementFactory(resolve.getProject()).getArrayClass(PsiUtil.getLanguageLevel(resolve))) {
final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters();
if (typeParameters.length == 1) {
final PsiType arrayComponentType = subst.substitute(typeParameters[0]);
if (arrayComponentType == null) {
return false;
}
methodReturnType = arrayComponentType.createArrayType();
}
}
containingClass = (PsiClass)resolve;
}
if (methodReturnType == null) {
if (containingClass == null) {
return false;
}
methodReturnType = JavaPsiFacade.getElementFactory(getProject()).createType(containingClass, subst);
}
return TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType);
}
return false;
return PsiMethodReferenceUtil.isReturnTypeCompatible(this, result, left);
}
@Nullable

View File

@@ -26,7 +26,7 @@ class AlienTest {
static {
IInt i1 = MyTest::<error descr="Cannot resolve method 'abracadabra'">abracadabra</error>;
IInt i2 = <error descr="Bad return type in method reference: cannot convert void to int">MyTest::foo</error>;
IInt i2 = MyTest::<error descr="Invalid method reference: int cannot be converted to String">foo</error>;
IInt i3 = MyTest::<error descr="Cannot resolve method 'bar'">bar</error>;
<error descr="Incompatible types. Found: '<method reference>', required: 'AlienTest.IIntInt'">IIntInt i4 = MyTest::bar;</error>
IInt i5 = <error descr="Non-static method cannot be referenced from a static context">MyTest::baz</error>;

View File

@@ -13,7 +13,7 @@ class Foo<R> {
public void foo() {
reduce(Moo::new);
reduce<error descr="'reduce(Foo.Factory<S>)' in 'Foo' cannot be applied to '(<method reference>)'">(AMoo::new)</error>;
reduce(<error descr="Bad return type in method reference: cannot convert Foo<R>.AMoo to S">AMoo::new</error>);
reduce(AAMoo::new);
reduce(AAAMoo::new);
}

View File

@@ -19,7 +19,7 @@ class DefaultConstructor {
void test2() {
I1<Inner> i1 = Inner :: new;
<error descr="Incompatible types. Found: '<method reference>', required: 'DefaultConstructor.I1<java.lang.Integer>'">I1<Integer> i1Int = Inner :: new;</error>
I1<Integer> i1Int = <error descr="Bad return type in method reference: cannot convert DefaultConstructor.Outer.Inner to java.lang.Integer">Inner :: new</error>;
I2<Inner, Outer> i2 = Inner :: <error descr="Cannot resolve constructor 'Inner'">new</error>;
}
}

View File

@@ -39,7 +39,7 @@ class OnArrayTest {
ArrayReturnType<String[]> a1 = String[]::new;
ArrayReturnType<String[][]> a2 = String[][]::new;
<error descr="Incompatible types. Found: '<method reference>', required: 'OnArrayTest.ArrayReturnType<java.lang.String[]>'">ArrayReturnType<String[]> a3 = int[]::new;</error>
ArrayReturnType<String[]> a3 = <error descr="Bad return type in method reference: cannot convert int[] to java.lang.String[]">int[]::new</error>;
ObjectArrayReturnType a4 = Foo<?>[]::new;
ObjectArrayReturnType a5 = <error descr="Generic array creation">Foo<? extends String>[]</error>::new;