new inference: isAcceptable for lambda

This commit is contained in:
Anna Kozlova
2014-02-20 20:27:40 +01:00
parent de7f053c42
commit a4deb4da3d
9 changed files with 70 additions and 89 deletions
@@ -20,7 +20,6 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NonNls;
@@ -128,89 +127,6 @@ public class LambdaUtil {
return true;
}
private static boolean checkRawAcceptable(PsiLambdaExpression expression, PsiType functionalInterfaceType) {
PsiElement parent = expression.getParent();
while (parent instanceof PsiParenthesizedExpression) {
parent = parent.getParent();
}
if (parent instanceof PsiExpressionList) {
final PsiElement gParent = parent.getParent();
if (gParent instanceof PsiMethodCallExpression) {
final PsiExpression qualifierExpression = ((PsiMethodCallExpression)gParent).getMethodExpression().getQualifierExpression();
final PsiType type = qualifierExpression != null ? qualifierExpression.getType() : null;
if (type instanceof PsiClassType && ((PsiClassType)type).isRaw()) {
return true;
}
final PsiMethod method = ((PsiMethodCallExpression)gParent).resolveMethod();
if (method != null) {
int lambdaIdx = getLambdaIdx((PsiExpressionList)parent, expression);
final PsiParameter[] parameters = method.getParameterList().getParameters();
final PsiType normalizedType = getNormalizedType(parameters[adjustLambdaIdx(lambdaIdx, method, parameters)]);
if (normalizedType instanceof PsiClassType && ((PsiClassType)normalizedType).isRaw()) return true;
}
}
if (functionalInterfaceType instanceof PsiClassType && ((PsiClassType)functionalInterfaceType).isRaw()){
return false;
}
}
return true;
}
public static boolean isAcceptable(PsiLambdaExpression lambdaExpression, final PsiType leftType, boolean checkReturnType) {
if (leftType instanceof PsiIntersectionType) {
for (PsiType conjunctType : ((PsiIntersectionType)leftType).getConjuncts()) {
if (isAcceptable(lambdaExpression, conjunctType, checkReturnType)) return true;
}
return false;
}
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(GenericsUtil.eliminateWildcards(leftType));
final PsiClass psiClass = resolveResult.getElement();
if (psiClass instanceof PsiAnonymousClass) {
return isAcceptable(lambdaExpression, ((PsiAnonymousClass)psiClass).getBaseClassType(), checkReturnType);
}
final MethodSignature methodSignature = getFunction(psiClass);
if (methodSignature == null) return false;
final PsiParameter[] lambdaParameters = lambdaExpression.getParameterList().getParameters();
final PsiType[] parameterTypes = methodSignature.getParameterTypes();
if (lambdaParameters.length != parameterTypes.length) return false;
for (int lambdaParamIdx = 0, length = lambdaParameters.length; lambdaParamIdx < length; lambdaParamIdx++) {
PsiParameter parameter = lambdaParameters[lambdaParamIdx];
final PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
final PsiType lambdaFormalType = typeElement.getType();
final PsiType methodParameterType = parameterTypes[lambdaParamIdx];
if (lambdaFormalType instanceof PsiPrimitiveType) {
if (methodParameterType instanceof PsiPrimitiveType) return methodParameterType.equals(lambdaFormalType);
return false;
}
if (!TypeConversionUtil.erasure(lambdaFormalType)
.isAssignableFrom(TypeConversionUtil.erasure(GenericsUtil.eliminateWildcards(
resolveResult.getSubstitutor().substitute(methodSignature.getSubstitutor().substitute(methodParameterType)))))) {
return false;
}
}
}
if (checkReturnType) {
final String uniqueVarName =
JavaCodeStyleManager.getInstance(lambdaExpression.getProject()).suggestUniqueVariableName("l", lambdaExpression, true);
String canonicalText = leftType.getCanonicalText();
if (leftType instanceof PsiEllipsisType) {
canonicalText = ((PsiEllipsisType)leftType).toArrayType().getCanonicalText();
}
final PsiStatement assignmentFromText = JavaPsiFacade.getElementFactory(lambdaExpression.getProject())
.createStatementFromText(canonicalText + " " + uniqueVarName + " = " + lambdaExpression.getText(), lambdaExpression);
final PsiLocalVariable localVariable = (PsiLocalVariable)((PsiDeclarationStatement)assignmentFromText).getDeclaredElements()[0];
LOG.assertTrue(psiClass != null);
PsiType methodReturnType = getReturnType(psiClass, methodSignature);
if (methodReturnType != null) {
methodReturnType = resolveResult.getSubstitutor().substitute(methodSignature.getSubstitutor().substitute(methodReturnType));
return LambdaHighlightingUtil.checkReturnTypeCompatible((PsiLambdaExpression)localVariable.getInitializer(), methodReturnType) == null;
}
}
return true;
}
@Nullable
static MethodSignature getFunction(PsiClass psiClass) {
if (psiClass == null) return null;
@@ -53,4 +53,6 @@ public interface PsiLambdaExpression extends PsiExpression {
* @return true when lambda declares parameter types explicitly
*/
boolean hasFormalParameterTypes();
boolean isAcceptable(PsiType leftType, boolean checkReturnType);
}
@@ -716,7 +716,7 @@ public class TypeConversionUtil {
final PsiType lType = lLambdaExpression.getFunctionalInterfaceType();
return Comparing.equal(rType, lType);
}
return !(left instanceof PsiArrayType) && LambdaUtil.isAcceptable(rLambdaExpression, left, false);
return !(left instanceof PsiArrayType) && rLambdaExpression.isAcceptable(left, false);
}
if (left instanceof PsiIntersectionType) {
@@ -123,6 +123,10 @@ public class FunctionalInterfaceParameterizationUtil {
return parameterization;
}
if (!parameterization.isAssignableFrom(psiClassType)) {
return null;
}
return getNonWildcardParameterization(parameterization);
}
return null;
@@ -747,6 +747,7 @@ public class InferenceSession {
LOG.assertTrue(var.getInstantiation() == PsiType.NULL);
final PsiTypeParameter typeParameter = var.getParameter();
final PsiType eqBound = getEqualsBound(var, substitutor);
if (eqBound != PsiType.NULL && eqBound instanceof PsiPrimitiveType) continue;
final PsiType lub = eqBound != PsiType.NULL && (myErased || eqBound != null) ? eqBound : getLowerBound(var, substitutor);
if (lub != PsiType.NULL) {
substitutor = substitutor.put(typeParameter, lub);
@@ -17,6 +17,7 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.controlFlow.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
@@ -25,6 +26,7 @@ import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.IntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -142,4 +144,61 @@ public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements Psi
}
return true;
}
@Override
public boolean isAcceptable(PsiType leftType, boolean checkReturnType) {
if (leftType instanceof PsiIntersectionType) {
for (PsiType conjunctType : ((PsiIntersectionType)leftType).getConjuncts()) {
if (isAcceptable(conjunctType, checkReturnType)) return true;
}
return false;
}
leftType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(leftType, this);
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(leftType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass instanceof PsiAnonymousClass) {
return isAcceptable(((PsiAnonymousClass)psiClass).getBaseClassType(), checkReturnType);
}
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
if (interfaceMethod == null) return false;
final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult);
assert leftType != null;
final PsiParameter[] lambdaParameters = getParameterList().getParameters();
final PsiType[] parameterTypes = interfaceMethod.getSignature(substitutor).getParameterTypes();
if (lambdaParameters.length != parameterTypes.length) return false;
for (int lambdaParamIdx = 0, length = lambdaParameters.length; lambdaParamIdx < length; lambdaParamIdx++) {
PsiParameter parameter = lambdaParameters[lambdaParamIdx];
final PsiTypeElement typeElement = parameter.getTypeElement();
if (typeElement != null) {
final PsiType lambdaFormalType = typeElement.getType();
final PsiType methodParameterType = parameterTypes[lambdaParamIdx];
if (!lambdaFormalType.equals(methodParameterType)) {
return false;
}
}
}
if (checkReturnType) {
final String uniqueVarName = JavaCodeStyleManager.getInstance(getProject()).suggestUniqueVariableName("l", this, true);
String canonicalText = leftType.getCanonicalText();
if (leftType instanceof PsiEllipsisType) {
canonicalText = ((PsiEllipsisType)leftType).toArrayType().getCanonicalText();
}
final PsiStatement assignmentFromText = JavaPsiFacade.getElementFactory(getProject())
.createStatementFromText(canonicalText + " " + uniqueVarName + " = " + getText(), this);
final PsiLocalVariable localVariable = (PsiLocalVariable)((PsiDeclarationStatement)assignmentFromText).getDeclaredElements()[0];
PsiType methodReturnType = interfaceMethod.getReturnType();
if (methodReturnType != null) {
return LambdaHighlightingUtil.checkReturnTypeCompatible((PsiLambdaExpression)localVariable.getInitializer(),
substitutor.substitute(methodReturnType)) == null;
}
}
return true;
}
}
@@ -121,8 +121,7 @@ public class JavaMethodsConflictResolver implements PsiConflictResolver{
if (methodParameters.length == 0) continue;
final PsiParameter param = i < methodParameters.length ? methodParameters[i] : methodParameters[methodParameters.length - 1];
final PsiType paramType = param.getType();
if (!LambdaUtil.isAcceptable(lambdaExpression, ((MethodCandidateInfo)conflict).getSubstitutor(false).substitute(paramType),
lambdaExpression.hasFormalParameterTypes())) {
if (!lambdaExpression.isAcceptable(((MethodCandidateInfo)conflict).getSubstitutor(false).substitute(paramType), lambdaExpression.hasFormalParameterTypes())) {
iterator.remove();
}
}
@@ -8,7 +8,7 @@ class Test {
void foo(I<? extends String, ? extends List<? extends String>> fip) { }
void test() {
foo<error descr="'foo(Test.I<? extends java.lang.String,? extends java.util.List<? extends java.lang.String>>)' in 'Test' cannot be applied to '(<lambda expression>)'">((ArrayList<? extends String> p) -> p.get(0))</error>;
foo(<error descr="Cannot infer functional interface type">(ArrayList<? extends String> p) -> p.get(0)</error>);
}
}
@@ -32,6 +32,6 @@ class ReturnTypeCompatibility {
call((String i)->{ return i;});
call(i->{ return i;});
call(i->"");
call<error descr="'call(ReturnTypeCompatibility.I1<int>)' in 'ReturnTypeCompatibility' cannot be applied to '(<lambda expression>)'">((int i)->{ return i;})</error>;
call(<error descr="Incompatible return type int in lambda expression">(int i)->{ return i;}</error>);
}
}