method refs: infer from functional interface return types

This commit is contained in:
anna
2012-10-05 10:39:24 +02:00
parent 4eeaeb4f2f
commit 9488b020db
4 changed files with 199 additions and 39 deletions
@@ -16,9 +16,11 @@
package com.intellij.psi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.infos.MethodCandidateInfo;
@@ -43,7 +45,7 @@ public class LambdaUtil {
}
@Nullable
public static PsiType getFunctionalInterfaceReturnType(PsiType functionalInterfaceType) {
public static PsiType getFunctionalInterfaceReturnType(@Nullable PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass != null) {
@@ -596,15 +598,19 @@ public class LambdaUtil {
final MethodSignature signature2 = ((PsiMethod)resolve).getSignature(subst);
final PsiType interfaceReturnType = getFunctionalInterfaceReturnType(left);
final PsiType methodReturnType = subst.substitute(((PsiMethod)resolve).getReturnType());
if (interfaceReturnType != null && methodReturnType != null && interfaceReturnType != PsiType.VOID &&
!TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType)) return false;
PsiType methodReturnType = subst.substitute(((PsiMethod)resolve).getReturnType());
if (interfaceReturnType != null && interfaceReturnType != PsiType.VOID) {
if (methodReturnType == null) {
methodReturnType = JavaPsiFacade.getElementFactory(methodReferenceExpression.getProject()).createType(((PsiMethod)resolve).getContainingClass(), subst);
}
if (!TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false)) return false;
}
if (areAcceptable(signature1, signature2, classRef.get(), substRef.get(), ((PsiMethod)resolve).isVarArgs())) return true;
} else if (resolve instanceof PsiClass) {
final PsiType interfaceReturnType = getFunctionalInterfaceReturnType(left);
if (interfaceReturnType != null) {
final PsiClassType classType = JavaPsiFacade.getElementFactory(methodReferenceExpression.getProject()).createType((PsiClass)resolve, result.getSubstitutor());
if (TypeConversionUtil.isAssignable(interfaceReturnType, classType)) {
if (TypeConversionUtil.isAssignable(interfaceReturnType, classType, false)) {
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length == 0) return true;
if (parameters.length == 1) {
@@ -751,6 +757,36 @@ public class LambdaUtil {
return getFunctionalInterfaceReturnType(functionalInterfaceType);
}
public static PsiSubstitutor inferFromReturnType(final PsiTypeParameter[] typeParameters,
final PsiType returnType,
@Nullable final PsiType interfaceMethodReturnType,
PsiSubstitutor psiSubstitutor,
final LanguageLevel languageLevel,
final Project project) {
if (interfaceMethodReturnType == null) return psiSubstitutor;
final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(project).getResolveHelper();
for (PsiTypeParameter typeParameter : typeParameters) {
final PsiType constraint = resolveHelper.getSubstitutionForTypeParameter(typeParameter, returnType, interfaceMethodReturnType, false, languageLevel);
if (constraint != PsiType.NULL) {
PsiType inferredType = null;
final PsiClassType[] bounds = typeParameter.getExtendsListTypes();
for (PsiClassType classTypeBound : bounds) {
if (TypeConversionUtil.isAssignable(classTypeBound, constraint)) {
inferredType = constraint;
break;
}
}
if (bounds.length == 0) {
inferredType = constraint;
}
if (inferredType != null) {
psiSubstitutor = psiSubstitutor.put(typeParameter, inferredType);
}
}
}
return psiSubstitutor;
}
private static class TypeParamsChecker extends PsiTypeVisitor<Boolean> {
private PsiMethod myMethod;
private final PsiClass myClass;
@@ -19,6 +19,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy;
@@ -235,7 +236,7 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
final boolean beginsWithReferenceType = process(classRef, substRef);
final PsiClass containingClass = classRef.get();
final PsiSubstitutor substitutor = substRef.get();
PsiSubstitutor substitutor = substRef.get();
if (containingClass != null) {
final PsiElement element = getReferenceNameElement();
@@ -251,31 +252,31 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
}
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
final PsiType interfaceMethodReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(PsiMethodReferenceExpressionImpl.this);
if (isConstructor && interfaceMethod != null) {
final PsiTypeParameter[] typeParameters = containingClass.getTypeParameters();
final boolean isRawSubst = PsiUtil.isRawSubstitutor(containingClass, substitutor);
final PsiClassType returnType = JavaPsiFacade.getElementFactory(getProject()).createType(containingClass,
isRawSubst ? PsiSubstitutor.EMPTY : substitutor);
substitutor = LambdaUtil.inferFromReturnType(typeParameters, returnType, interfaceMethodReturnType, substitutor, languageLevel,
PsiMethodReferenceExpressionImpl.this.getProject());
if (containingClass.getConstructors().length == 0 &&
!containingClass.isEnum() &&
!containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
return new JavaResolveResult[]{new ClassCandidateInfo(containingClass, substitutor)};
}
}
final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(resolveResult.getSubstitutor()) : null;
final MethodReferenceConflictResolver conflictResolver =
new MethodReferenceConflictResolver(containingClass, substitutor, signature, beginsWithReferenceType);
final PsiConflictResolver[] resolvers;
if (signature != null) {
if (isConstructor && containingClass.getConstructors().length == 0 && !containingClass.isEnum() && !containingClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
return new JavaResolveResult[]{new ClassCandidateInfo(containingClass, substitutor)};
}
final PsiType[] parameterTypes = signature.getParameterTypes();
resolvers = new PsiConflictResolver[]{conflictResolver,
new JavaMethodsConflictResolver(PsiMethodReferenceExpressionImpl.this, parameterTypes) {
@Override
public CandidateInfo resolveConflict(List<CandidateInfo> conflicts) {
boolean varargs = false;
for (CandidateInfo conflict : conflicts) {
final PsiElement psiElement = conflict.getElement();
if (psiElement instanceof PsiMethod && ((PsiMethod)psiElement).isVarArgs()) {
varargs = true;
break;
}
}
checkSpecifics(conflicts, varargs ? MethodCandidateInfo.ApplicabilityLevel.VARARGS : MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY);
return conflicts.size() == 1 ? conflicts.get(0) : null;
}
}};
resolvers = new PsiConflictResolver[]{conflictResolver, new MethodRefsSpecificResolver(parameterTypes)};
}
else {
resolvers = new PsiConflictResolver[]{conflictResolver};
@@ -284,26 +285,16 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
new MethodCandidatesProcessor(PsiMethodReferenceExpressionImpl.this, resolvers, new SmartList<CandidateInfo>()) {
@Override
protected MethodCandidateInfo createCandidateInfo(final PsiMethod method,
PsiSubstitutor substitutor,
boolean staticProblem,
boolean accessible) {
final PsiSubstitutor substitutor,
final boolean staticProblem,
final boolean accessible) {
final PsiExpressionList argumentList = getArgumentList();
return new MethodCandidateInfo(method, substitutor, !accessible, staticProblem, argumentList, myCurrentFileContext,
argumentList != null ? argumentList.getExpressionTypes() : null, getTypeArguments(),
getLanguageLevel()) {
@Override
public PsiSubstitutor inferTypeArguments(ParameterTypeInferencePolicy policy) {
if (signature == null) return PsiSubstitutor.EMPTY;
final PsiType[] types = method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes();
final PsiType[] rightTypes = signature.getParameterTypes();
if (types.length < rightTypes.length) {
return PsiUtil.resolveGenericsClassInType(rightTypes[0]).getSubstitutor();
} else if (types.length > rightTypes.length) {
return PsiUtil.resolveGenericsClassInType(types[0]).getSubstitutor();
}
return JavaPsiFacade.getInstance(getProject()).getResolveHelper()
.inferTypeArguments(method.getTypeParameters(), types, rightTypes,
PsiUtil.getLanguageLevel(PsiMethodReferenceExpressionImpl.this));
return inferTypeArgumentsFromInterfaceMethod(signature, interfaceMethodReturnType, method, substitutor, languageLevel);
}
};
}
@@ -337,6 +328,31 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
return JavaResolveResult.EMPTY_ARRAY;
}
private PsiSubstitutor inferTypeArgumentsFromInterfaceMethod(@Nullable MethodSignature signature,
@Nullable PsiType interfaceMethodReturnType,
PsiMethod method,
PsiSubstitutor substitutor,
LanguageLevel languageLevel) {
if (signature == null) return PsiSubstitutor.EMPTY;
final PsiType[] types = method.getSignature(substitutor).getParameterTypes();
final PsiType[] rightTypes = signature.getParameterTypes();
if (types.length < rightTypes.length) {
return PsiUtil.resolveGenericsClassInType(rightTypes[0]).getSubstitutor();
} else if (types.length > rightTypes.length) {
return PsiUtil.resolveGenericsClassInType(types[0]).getSubstitutor();
}
PsiSubstitutor psiSubstitutor = JavaPsiFacade.getInstance(getProject()).getResolveHelper().inferTypeArguments(
method.getTypeParameters(), types, rightTypes, languageLevel);
psiSubstitutor = psiSubstitutor.putAll(substitutor);
return LambdaUtil.inferFromReturnType(method.getTypeParameters(),
psiSubstitutor.substitute(method.getReturnType()),
interfaceMethodReturnType,
psiSubstitutor,
languageLevel, PsiMethodReferenceExpressionImpl.this.getProject());
}
private class MethodReferenceConflictResolver implements PsiConflictResolver {
private final PsiClass myContainingClass;
private final PsiSubstitutor mySubstitutor;
@@ -418,5 +434,26 @@ public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase
return !firstCandidates.isEmpty() ? firstCandidates.get(0) : secondCandidates.get(0);
}
}
private class MethodRefsSpecificResolver extends JavaMethodsConflictResolver {
public MethodRefsSpecificResolver(PsiType[] parameterTypes) {
super(PsiMethodReferenceExpressionImpl.this, parameterTypes);
}
@Override
public CandidateInfo resolveConflict(List<CandidateInfo> conflicts) {
boolean varargs = false;
for (CandidateInfo conflict : conflicts) {
final PsiElement psiElement = conflict.getElement();
if (psiElement instanceof PsiMethod && ((PsiMethod)psiElement).isVarArgs()) {
varargs = true;
break;
}
}
checkSpecifics(conflicts,
varargs ? MethodCandidateInfo.ApplicabilityLevel.VARARGS : MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY);
return conflicts.size() == 1 ? conflicts.get(0) : null;
}
}
}
}
@@ -0,0 +1,83 @@
class MyTestDefaultConstructor {
static class SuperFoo<<warning descr="Type parameter 'X' is never used">X</warning>> { }
static class Foo<X extends Number> extends SuperFoo<X> {
}
interface I1 {
SuperFoo<String> _();
}
interface I2 {
SuperFoo<Integer> _();
}
interface I3 {
SuperFoo<Object> _();
}
private static void <warning descr="Private method 'foo(MyTestDefaultConstructor.I1)' is never used">foo</warning>(I1 i) {System.out.println(i);}
private static void foo(I2 i) {System.out.println(i);}
private static void <warning descr="Private method 'foo(MyTestDefaultConstructor.I3)' is never used">foo</warning>(I3 i) {System.out.println(i);}
static {
foo(Foo::new);
}
}
class MyTestConstructor {
static class SuperFoo<<warning descr="Type parameter 'X' is never used">X</warning>> { }
static class Foo<X extends Number> extends SuperFoo<X> {
Foo(){}
}
interface I1 {
SuperFoo<String> _();
}
interface I2 {
SuperFoo<Integer> _();
}
interface I3 {
SuperFoo<Object> _();
}
private static void <warning descr="Private method 'foo(MyTestConstructor.I1)' is never used">foo</warning>(I1 i) {System.out.println(i);}
private static void foo(I2 i) {System.out.println(i);}
private static void <warning descr="Private method 'foo(MyTestConstructor.I3)' is never used">foo</warning>(I3 i) {System.out.println(i);}
static {
foo(Foo::new);
}
}
class MyTestMethod {
static class SuperFoo<<warning descr="Type parameter 'X' is never used">X</warning>> { }
static class Foo<X extends Number> extends SuperFoo<X> {
}
interface I1 {
SuperFoo<String> _();
}
interface I2 {
SuperFoo<Integer> _();
}
interface I3 {
SuperFoo<Object> _();
}
static <X extends Number> Foo<X> m() { return null; }
private static void <warning descr="Private method 'foo(MyTestMethod.I1)' is never used">foo</warning>(I1 i) {System.out.println(i);}
private static void foo(I2 i) {System.out.println(i);}
private static void <warning descr="Private method 'foo(MyTestMethod.I3)' is never used">foo</warning>(I3 i) {System.out.println(i);}
static {
foo(MyTestMethod::m);
}
}
@@ -105,6 +105,10 @@ public class MethodRefHighlightingTest extends LightDaemonAnalyzerTestCase {
doTest();
}
public void testInferenceFromReturnType() throws Exception {
doTest(true);
}
public void testReturnTypeSpecific() throws Exception {
doTest(true);
}