lambda: infer from return type; cyclic inference stop

This commit is contained in:
Anna Kozlova
2012-08-13 11:29:40 +04:00
parent bff2c6d55c
commit 5ba5425a54
22 changed files with 642 additions and 207 deletions
@@ -239,6 +239,23 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {
myHolder.add(HighlightUtil.checkLambdaFeature(expression));
if (!myHolder.hasErrorResults()) {
if (LambdaUtil.isValidLambdaContext(expression.getParent())) {
final PsiType functionalInterfaceType = expression.getFunctionalInterfaceType();
if (functionalInterfaceType != null) {
final String notFunctionalMessage = LambdaUtil.checkInterfaceFunctional(functionalInterfaceType);
if (notFunctionalMessage != null) {
myHolder.add(HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, notFunctionalMessage));
} else {
if (!LambdaUtil.isLambdaFullyInferred(expression, functionalInterfaceType)) {
myHolder.add(HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, "Cyclic inference")); //todo[ann] append not inferred type params info
}
}
}
} else {
myHolder.add(HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, expression, "Lambda expression not expected here"));
}
}
}
@Override
@@ -32,86 +32,71 @@ import java.util.List;
public class LambdaUtil {
private static final Logger LOG = Logger.getInstance("#" + LambdaUtil.class.getName());
@Nullable
public static PsiType getFunctionalInterfaceReturnType(PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass != null) {
final MethodSignature methodSignature = getFunction(psiClass);
if (methodSignature != null) {
final PsiType returnType = getReturnType(psiClass, methodSignature);
return resolveResult.getSubstitutor().substitute(returnType);
}
}
return null;
}
@Nullable
public static PsiMethod getFunctionalInterfaceMethod(PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass != null) {
final MethodSignature methodSignature = getFunction(psiClass);
if (methodSignature != null) {
return getMethod(psiClass, methodSignature);
}
}
return null;
}
public static boolean isValidLambdaContext(PsiElement context) {
return context instanceof PsiTypeCastExpression ||
context instanceof PsiAssignmentExpression ||
context instanceof PsiVariable ||
context instanceof PsiLambdaExpression ||
context instanceof PsiReturnStatement ||
context instanceof PsiExpressionList ||
context instanceof PsiParenthesizedExpression;
}
public static boolean isLambdaFullyInferred(PsiLambdaExpression expression, PsiType functionalInterfaceType) {
if (expression.getParameterList().getParametersCount() > 0 || getFunctionalInterfaceReturnType(functionalInterfaceType) != PsiType.VOID) { //todo check that void lambdas without params check
final Boolean accept = functionalInterfaceType.accept(new TypeParamsChecker(expression));
return accept == null || !accept.booleanValue();
}
return true;
}
@Nullable
public static String checkInterfaceFunctional(PsiType functionalInterfaceType) {
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(functionalInterfaceType);
if (aClass != null) {
return checkInterfaceFunctional(aClass);
}
return null;
}
@Nullable
public static String checkInterfaceFunctional(@NotNull PsiClass psiClass) {
if (psiClass instanceof PsiTypeParameter) return null; //should be logged as cyclic inference
final List<MethodSignature> signatures = findFunctionCandidates(psiClass);
if (signatures == null) return "Target type of a lambda conversion must be an interface";
if (signatures.isEmpty()) return "No target method found";
return signatures.size() == 1 ? null : "Multiple non-overriding abstract methods found";
}
private static boolean overridesPublicObjectMethod(PsiMethod psiMethod) {
boolean overrideObject = false;
for (PsiMethod superMethod : psiMethod.findDeepestSuperMethods()) {
final PsiClass containingClass = superMethod.getContainingClass();
if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
if (superMethod.hasModifierProperty(PsiModifier.PUBLIC)) {
overrideObject = true;
break;
}
}
}
return overrideObject;
}
private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) {
final MethodSignature methodSignature;
if (containingClass != null && containingClass != psiClass) {
methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY));
}
else {
methodSignature = method.getSignature(PsiSubstitutor.EMPTY);
}
return methodSignature;
}
@Nullable
private static List<MethodSignature> hasSubsignature(List<MethodSignature> signatures) {
for (MethodSignature signature : signatures) {
boolean subsignature = true;
for (MethodSignature methodSignature : signatures) {
if (!signature.equals(methodSignature)) {
if (!MethodSignatureUtil.isSubsignature(signature, methodSignature)) {
subsignature = false;
break;
}
}
}
if (subsignature) return Collections.singletonList(signature);
}
return signatures;
}
@Nullable
private static List<MethodSignature> findFunctionCandidates(PsiClass psiClass) {
if (psiClass.isInterface()) {
final List<MethodSignature> methods = new ArrayList<MethodSignature>();
final PsiMethod[] psiClassMethods = psiClass.getAllMethods();
for (PsiMethod psiMethod : psiClassMethods) {
if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
final PsiClass methodContainingClass = psiMethod.getContainingClass();
if (!overridesPublicObjectMethod(psiMethod)) {
methods.add(getMethodSignature(psiMethod, psiClass, methodContainingClass));
}
}
return hasSubsignature(methods);
}
return null;
}
@Nullable
public static MethodSignature getFunction(PsiClass psiClass) {
if (psiClass == null) return null;
final List<MethodSignature> functions = findFunctionCandidates(psiClass);
if (functions != null && functions.size() == 1) {
return functions.get(0);
}
return null;
}
public static PsiType getLambdaParameterType(PsiParameter param) {
final PsiElement paramParent = param.getParent();
if (paramParent instanceof PsiParameterList) {
final int parameterIndex = ((PsiParameterList)paramParent).getParameterIndex(param);
@@ -130,7 +115,7 @@ public class LambdaUtil {
if (bound != null) {
return bound;
}
}
}
return psiType;
}
}
@@ -197,27 +182,19 @@ public class LambdaUtil {
if (typeElement != null) {
final PsiType lambdaFormalType = typeElement.getType();
final PsiType methodParameterType = parameterTypes[lambdaParamIdx];
if (lambdaFormalType instanceof PsiPrimitiveType){
if (lambdaFormalType instanceof PsiPrimitiveType) {
if (methodParameterType instanceof PsiPrimitiveType) return methodParameterType.isAssignableFrom(lambdaFormalType);
return false;
}
if (!resolveResult.getSubstitutor().substitute(methodSignature.getSubstitutor().substitute(methodParameterType)).isAssignableFrom(lambdaFormalType)) {
if (!resolveResult.getSubstitutor().substitute(methodSignature.getSubstitutor().substitute(methodParameterType))
.isAssignableFrom(lambdaFormalType)) {
return false;
}
}
}
PsiMethod method = null;
LOG.assertTrue(psiClass != null);
final PsiMethod[] methodsByName = psiClass.findMethodsByName(methodSignature.getName(), true);
for (PsiMethod psiMethod : methodsByName) {
if (MethodSignatureUtil.areSignaturesEqual(getMethodSignature(psiMethod, psiClass, psiMethod.getContainingClass()), methodSignature)) {
method = psiMethod;
break;
}
}
LOG.assertTrue(method != null);
PsiType methodReturnType = method.getReturnType();
PsiType methodReturnType = getReturnType(psiClass, methodSignature);
if (methodReturnType != null && methodReturnType != PsiType.VOID) {
methodReturnType = resolveResult.getSubstitutor().substitute(methodSignature.getSubstitutor().substitute(methodReturnType));
final PsiElement body = lambdaExpression.getBody();
@@ -231,14 +208,108 @@ public class LambdaUtil {
}
}
}
} else if (body instanceof PsiExpression) {
}
else if (body instanceof PsiExpression) {
return checkReturnTypeAssignability(((PsiExpression)body).getType(), parameterTypes, lambdaExpression, methodReturnType);
}
}
return true;
}
private static boolean checkReturnTypeAssignability(PsiType lambdaReturnType, PsiType[] parameterTypes, PsiLambdaExpression lambdaExpression, PsiType methodReturnType) {
@Nullable
private static MethodSignature getFunction(PsiClass psiClass) {
if (psiClass == null) return null;
final List<MethodSignature> functions = findFunctionCandidates(psiClass);
if (functions != null && functions.size() == 1) {
return functions.get(0);
}
return null;
}
private static boolean overridesPublicObjectMethod(PsiMethod psiMethod) {
boolean overrideObject = false;
for (PsiMethod superMethod : psiMethod.findDeepestSuperMethods()) {
final PsiClass containingClass = superMethod.getContainingClass();
if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
if (superMethod.hasModifierProperty(PsiModifier.PUBLIC)) {
overrideObject = true;
break;
}
}
}
return overrideObject;
}
private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) {
final MethodSignature methodSignature;
if (containingClass != null && containingClass != psiClass) {
methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY));
}
else {
methodSignature = method.getSignature(PsiSubstitutor.EMPTY);
}
return methodSignature;
}
@Nullable
private static List<MethodSignature> hasSubsignature(List<MethodSignature> signatures) {
for (MethodSignature signature : signatures) {
boolean subsignature = true;
for (MethodSignature methodSignature : signatures) {
if (!signature.equals(methodSignature)) {
if (!MethodSignatureUtil.isSubsignature(signature, methodSignature)) {
subsignature = false;
break;
}
}
}
if (subsignature) return Collections.singletonList(signature);
}
return signatures;
}
@Nullable
private static List<MethodSignature> findFunctionCandidates(PsiClass psiClass) {
if (psiClass.isInterface()) {
final List<MethodSignature> methods = new ArrayList<MethodSignature>();
final PsiMethod[] psiClassMethods = psiClass.getAllMethods();
for (PsiMethod psiMethod : psiClassMethods) {
if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
final PsiClass methodContainingClass = psiMethod.getContainingClass();
if (!overridesPublicObjectMethod(psiMethod)) {
methods.add(getMethodSignature(psiMethod, psiClass, methodContainingClass));
}
}
return hasSubsignature(methods);
}
return null;
}
@Nullable
private static PsiType getReturnType(PsiClass psiClass, MethodSignature methodSignature) {
final PsiMethod method = getMethod(psiClass, methodSignature);
return method != null ? method.getReturnType() : null;
}
@Nullable
private static PsiMethod getMethod(PsiClass psiClass, MethodSignature methodSignature) {
final PsiMethod[] methodsByName = psiClass.findMethodsByName(methodSignature.getName(), true);
for (PsiMethod psiMethod : methodsByName) {
if (MethodSignatureUtil
.areSignaturesEqual(getMethodSignature(psiMethod, psiClass, psiMethod.getContainingClass()), methodSignature)) {
return psiMethod;
}
}
return null;
}
private static boolean checkReturnTypeAssignability(PsiType lambdaReturnType,
PsiType[] parameterTypes,
PsiLambdaExpression lambdaExpression,
PsiType methodReturnType) {
if (lambdaReturnType instanceof PsiLambdaParameterType) {
final PsiParameter parameter = ((PsiLambdaParameterType)lambdaReturnType).getParameter();
final int parameterIndex = lambdaExpression.getParameterList().getParameterIndex(parameter);
@@ -248,4 +319,58 @@ public class LambdaUtil {
}
return lambdaReturnType != null && methodReturnType.isAssignableFrom(lambdaReturnType);
}
private static class TypeParamsChecker extends PsiTypeVisitor<Boolean> {
private final PsiLambdaExpression myExpression;
public TypeParamsChecker(PsiLambdaExpression expression) {
myExpression = expression;
}
@Override
public Boolean visitClassType(PsiClassType classType) {
for (PsiType paramType : classType.getParameters()) {
final Boolean paramAccepted = paramType.accept(this);
if (paramAccepted != null && paramAccepted.booleanValue()) return true;
}
final PsiClass resolve = classType.resolve();
if (resolve instanceof PsiTypeParameter) {
if (!PsiTreeUtil.isAncestor(((PsiTypeParameter)resolve).getOwner(), myExpression, false)) {
return true;
}
}
return false;
}
@Nullable
@Override
public Boolean visitWildcardType(PsiWildcardType wildcardType) {
final PsiType bound = wildcardType.getBound();
if (bound != null) return bound.accept(this);
return false;
}
@Nullable
@Override
public Boolean visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
return visitWildcardType(capturedWildcardType.getWildcard());
}
@Nullable
@Override
public Boolean visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) {
return true;
}
@Nullable
@Override
public Boolean visitArrayType(PsiArrayType arrayType) {
return arrayType.getComponentType().accept(this);
}
@Override
public Boolean visitType(PsiType type) {
return false;
}
}
}
@@ -18,6 +18,8 @@ package com.intellij.psi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Represents a Java lambda expression.
*/
@@ -38,4 +40,9 @@ public interface PsiLambdaExpression extends PsiExpression {
*/
@Nullable
PsiElement getBody();
List<PsiExpression> getReturnExpressions();
@Nullable
PsiType getFunctionalInterfaceType();
}
@@ -70,6 +70,7 @@ public class PsiTypeVisitor<A> {
@Nullable
public A visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) {
return visitType(LambdaUtil.getFunctionInterfaceType(lambdaExpressionType.getExpression()));
final PsiLambdaExpression lambdaExpression = lambdaExpressionType.getExpression();
return visitType(lambdaExpression.getFunctionalInterfaceType());
}
}
@@ -15,6 +15,7 @@
*/
package com.intellij.psi.infos;
import com.intellij.openapi.util.Key;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy;
@@ -27,6 +28,7 @@ import org.jetbrains.annotations.Nullable;
* @author ik, dsl
*/
public class MethodCandidateInfo extends CandidateInfo{
public static final Key<PsiMethod> CURRENT_CANDIDATE = Key.create("CURRENT_CANDIDATE");
@ApplicabilityLevelConstant private int myApplicabilityLevel = 0;
private final PsiElement myArgumentList;
private final PsiType[] myArgumentTypes;
@@ -90,7 +92,13 @@ public class MethodCandidateInfo extends CandidateInfo{
PsiSubstitutor incompleteSubstitutor = super.getSubstitutor();
PsiMethod method = getElement();
if (myTypeArguments == null) {
myCalcedSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE);
myArgumentList.putUserData(CURRENT_CANDIDATE, getElement());
try {
myCalcedSubstitutor = inferTypeArguments(DefaultParameterTypeInferencePolicy.INSTANCE);
}
finally {
myArgumentList.putUserData(CURRENT_CANDIDATE, null);
}
}
else {
PsiTypeParameter[] typeParams = method.getTypeParameters();
@@ -15,12 +15,15 @@
*/
package com.intellij.psi.impl.source.resolve;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.java.PsiLambdaExpressionImpl;
import com.intellij.psi.infos.CandidateInfo;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.scope.MethodProcessorSetupFailedException;
import com.intellij.psi.scope.processor.MethodCandidatesProcessor;
import com.intellij.psi.scope.processor.MethodResolverProcessor;
@@ -34,6 +37,8 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class PsiResolveHelperImpl implements PsiResolveHelper {
static final RecursionGuard ourGuard = RecursionManager.createGuard("typeArgInference");
private final PsiManager myManager;
@@ -143,33 +148,59 @@ public class PsiResolveHelperImpl implements PsiResolveHelper {
private static Pair<PsiType, ConstraintType> inferTypeForMethodTypeParameterInner(final PsiTypeParameter typeParameter,
final PsiParameter[] parameters,
PsiExpression[] arguments,
final PsiExpression[] arguments,
final PsiSubstitutor partialSubstitutor,
final PsiElement parent,
final ParameterTypeInferencePolicy policy) {
PsiType[] paramTypes = new PsiType[arguments.length];
PsiType[] argTypes = new PsiType[arguments.length];
if (parameters.length > 0) {
for (int j = 0; j < argTypes.length; j++) {
PsiExpression argument = arguments[j];
if (argument == null) continue;
if (argument instanceof PsiMethodCallExpression && ourGuard.currentStack().contains(argument)) continue;
argTypes[j] = argument.getType();
final PsiParameter parameter = parameters[Math.min(j, parameters.length - 1)];
if (j >= parameters.length && !parameter.isVarArgs()) break;
paramTypes[j] = parameter.getType();
if (paramTypes[j] instanceof PsiEllipsisType) {
paramTypes[j] = ((PsiEllipsisType)paramTypes[j]).getComponentType();
if (arguments.length == parameters.length && argTypes[j] instanceof PsiArrayType && !(((PsiArrayType)argTypes[j]).getComponentType() instanceof PsiPrimitiveType)) {
argTypes[j] = ((PsiArrayType)argTypes[j]).getComponentType();
}
}
}
}
return inferTypeForMethodTypeParameterInner(typeParameter, paramTypes, argTypes, partialSubstitutor, parent, policy);
}
private static Pair<PsiType, ConstraintType> inferTypeForMethodTypeParameterInner(final PsiTypeParameter typeParameter,
final PsiType[] paramTypes,
PsiType[] argTypes,
PsiSubstitutor partialSubstitutor,
PsiElement parent,
@Nullable PsiElement parent,
final ParameterTypeInferencePolicy policy) {
PsiWildcardType wildcardToCapture = null;
PsiType lowerBound = PsiType.NULL;
PsiType upperBound = PsiType.NULL;
if (parameters.length > 0) {
for (int j = 0; j < arguments.length; j++) {
PsiExpression argument = arguments[j];
if (argument == null) continue;
if (argument instanceof PsiMethodCallExpression && ourGuard.currentStack().contains(argument)) continue;
final PsiParameter parameter = parameters[Math.min(j, parameters.length - 1)];
if (j >= parameters.length && !parameter.isVarArgs()) break;
PsiType parameterType = parameter.getType();
PsiType argumentType = argument.getType();
if (paramTypes.length > 0) {
for (int j = 0; j < argTypes.length; j++) {
PsiType argumentType = argTypes[j];
if (argumentType == null) continue;
if (j >= paramTypes.length) break;
PsiType parameterType = paramTypes[j];
if (parameterType == null) break;
if (parameterType instanceof PsiEllipsisType) {
parameterType = ((PsiEllipsisType)parameterType).getComponentType();
if (arguments.length == parameters.length && argumentType instanceof PsiArrayType && !(((PsiArrayType)argumentType).getComponentType() instanceof PsiPrimitiveType)) {
if (argTypes.length == paramTypes.length && argumentType instanceof PsiArrayType && !(((PsiArrayType)argumentType).getComponentType() instanceof PsiPrimitiveType)) {
argumentType = ((PsiArrayType)argumentType).getComponentType();
}
}
final Pair<PsiType,ConstraintType> currentSubstitution = getSubstitutionForTypeParameterConstraint(typeParameter, parameterType,
argumentType, true, PsiUtil.getLanguageLevel(argument));
argumentType, true, PsiUtil.getLanguageLevel(typeParameter));
if (currentSubstitution == null) continue;
if (currentSubstitution == FAILED_INFERENCE) {
return getFailedInferenceConstraint(typeParameter);
@@ -478,6 +509,10 @@ public class PsiResolveHelperImpl implements PsiResolveHelper {
}
if (paramClass == null) return null;
if (arg instanceof PsiLambdaExpressionType) {
return inferSubstitutionFromLambda(typeParam, (PsiLambdaExpressionType)arg);
}
if (!(arg instanceof PsiClassType)) return null;
JavaResolveResult argResult = ((PsiClassType)arg).resolveGenerics();
@@ -500,6 +535,94 @@ public class PsiResolveHelperImpl implements PsiResolveHelper {
return getSubstitutionForTypeParameterInner(param, arg, patternType, ConstraintType.SUPERTYPE, 0);
}
@Nullable
private static Pair<PsiType, ConstraintType> inferSubstitutionFromLambda(PsiTypeParameter typeParam, PsiLambdaExpressionType arg) {
final PsiLambdaExpression lambdaExpression = arg.getExpression();
if (PsiUtil.getLanguageLevel(lambdaExpression).isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiElement parent = skipParenthesizedExprUp(lambdaExpression.getParent());
if (parent instanceof PsiExpressionList) {
final PsiExpressionList expressionList = (PsiExpressionList)parent;
final PsiMethod method = expressionList.getUserData(MethodCandidateInfo.CURRENT_CANDIDATE);
if (method != null) {
final int i = ((PsiLambdaExpressionImpl)lambdaExpression).getLambdaIdx(expressionList);
if (i < 0) return null;
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length <= i) return null;
return inferConstraintFromFunctionalInterfaceMethod(typeParam, lambdaExpression, parameters[i].getType());
}
}
else {
return inferConstraintFromFunctionalInterfaceMethod(typeParam, lambdaExpression, lambdaExpression.getFunctionalInterfaceType());
}
}
return null;
}
@Nullable
private static Pair<PsiType, ConstraintType> inferConstraintFromFunctionalInterfaceMethod(PsiTypeParameter typeParam,
PsiLambdaExpression lambdaExpression,
PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
if (method != null) {
final Pair<PsiType, ConstraintType> constraintFromFormalParams = inferConstraintFromLambdaFormalParams(typeParam, resolveResult, method, lambdaExpression);
if (constraintFromFormalParams != null) return constraintFromFormalParams;
final PsiParameter[] methodParameters = method.getParameterList().getParameters();
final PsiSubstitutor subst = resolveResult.getSubstitutor();
final PsiType returnType = subst.substitute(method.getReturnType());
if (returnType != null && returnType != PsiType.VOID) {
final List<PsiExpression> expressions = lambdaExpression.getReturnExpressions();
for (final PsiExpression expression : expressions) {
PsiType exprType = ourGuard.doPreventingRecursion(lambdaExpression, true, new Computable<PsiType>() {
@Override
public PsiType compute() {
return expression.getType();
}
});
if (exprType instanceof PsiLambdaParameterType) {
final PsiParameter parameter = ((PsiLambdaParameterType)exprType).getParameter();
final int parameterIndex = lambdaExpression.getParameterList().getParameterIndex(parameter);
if (parameterIndex > -1) {
exprType = subst.substitute(methodParameters[parameterIndex].getType());
}
} else if (exprType instanceof PsiLambdaExpressionType) {
return inferConstraintFromFunctionalInterfaceMethod(typeParam, ((PsiLambdaExpressionType)exprType).getExpression(), returnType);
}
Pair<PsiType, ConstraintType> constraint =
getSubstitutionForTypeParameterConstraint(typeParam, returnType, exprType, false, PsiUtil.getLanguageLevel(method));
if (constraint != null) {
return constraint; //todo check that all return statements lead to the same inference
}
}
}
}
return null;
}
@Nullable
private static Pair<PsiType, ConstraintType> inferConstraintFromLambdaFormalParams(PsiTypeParameter typeParam,
PsiClassType.ClassResolveResult resolveResult,
PsiMethod method, PsiLambdaExpression lambdaExpression) {
final PsiParameter[] parameters = lambdaExpression.getParameterList().getParameters();
final PsiType[] lambdaArgs = new PsiType[parameters.length];
for (int i = 0; i < parameters.length; i++) {
PsiParameter parameter = parameters[i];
if (parameter.getTypeElement() == null) {
return null;
}
lambdaArgs[i] = parameter.getType();
}
final PsiSubstitutor subst = resolveResult.getSubstitutor();
final PsiParameter[] methodParameters = method.getParameterList().getParameters();
PsiType[] methodParamTypes = new PsiType[methodParameters.length];
for (int i = 0; i < methodParameters.length; i++) {
methodParamTypes[i] = subst.substitute(methodParameters[i].getType());
}
return inferTypeForMethodTypeParameterInner(typeParam, methodParamTypes, lambdaArgs, subst, null, DefaultParameterTypeInferencePolicy.INSTANCE);
}
private static PsiType intersectAllExtends(PsiTypeParameter typeParam, PsiType arg) {
if (arg == null) return null;
PsiClassType[] superTypes = typeParam.getSuperTypes();
@@ -666,6 +789,8 @@ public class PsiResolveHelperImpl implements PsiResolveHelper {
constraint = policy.inferTypeConstraintFromCallContext(methodCall, (PsiExpressionList)parent, (PsiCallExpression)pParent,
typeParameter);
}
} else if (parent instanceof PsiLambdaExpression) {
expectedType = LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)parent).getFunctionalInterfaceType());
}
final PsiManager manager = typeParameter.getManager();
@@ -698,6 +823,16 @@ public class PsiResolveHelperImpl implements PsiResolveHelper {
final Pair<PsiType, ConstraintType> result;
if (constraint == null) {
final PsiExpressionList argumentList = methodCall.getArgumentList();
if (argumentList != null && PsiUtil.getLanguageLevel(argumentList).isAtLeast(LanguageLevel.JDK_1_8)) {
for (PsiExpression expression : argumentList.getExpressions()) {
if (expression instanceof PsiLambdaExpression){
return getFailedInferenceConstraint(typeParameter);
}
}
}
final PsiSubstitutor finalSubstitutor = substitutor.put(typeParameter, null);
PsiClassType[] superTypes = typeParameter.getSuperTypes();
if (superTypes.length == 0) return null;
@@ -15,14 +15,22 @@
*/
package com.intellij.psi.impl.source.tree.java;
import com.intellij.openapi.util.*;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements PsiLambdaExpression {
public static RecursionGuard ourGuard = RecursionManager.createGuard("Lambda");
public PsiLambdaExpressionImpl() {
super(JavaElementType.LAMBDA_EXPRESSION);
}
@@ -39,6 +47,102 @@ public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements Psi
return element instanceof PsiExpression || element instanceof PsiCodeBlock ? element : null;
}
@Override
public List<PsiExpression> getReturnExpressions() {
final PsiElement body = getBody();
if (body instanceof PsiExpression) {
//if (((PsiExpression)body).getType() != PsiType.VOID) return Collections.emptyList();
return Collections.singletonList((PsiExpression)body);
}
final List<PsiExpression> result = new ArrayList<PsiExpression>();
if (body != null) {
body.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
final PsiExpression returnValue = statement.getReturnValue();
if (returnValue != null) {
result.add(returnValue);
}
}
@Override
public void visitClass(PsiClass aClass) {
}
});
}
return result;
}
@Nullable
@Override
public PsiType getFunctionalInterfaceType() {
PsiElement parent = getParent();
while (parent instanceof PsiParenthesizedExpression) {
parent = parent.getParent();
}
PsiType type = null;
if (parent instanceof PsiTypeCastExpression) {
type = ((PsiTypeCastExpression)parent).getType();
}
else if (parent instanceof PsiVariable) {
type = ((PsiVariable)parent).getType();
}
else if (parent instanceof PsiAssignmentExpression) {
final PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression();
type = lExpression.getType();
}
else if (parent instanceof PsiExpressionList) {
final PsiExpressionList expressionList = (PsiExpressionList)parent;
int lambdaIdx = getLambdaIdx(expressionList);
if (lambdaIdx > -1) {
final PsiElement gParent = expressionList.getParent();
if (gParent instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression contextCall = (PsiMethodCallExpression)gParent;
final JavaResolveResult resolveResult = contextCall.resolveMethodGenerics();
final PsiElement resolve = resolveResult.getElement();
if (resolve instanceof PsiMethod) {
final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters();
if (lambdaIdx < parameters.length) {
type = parameters[lambdaIdx].getType();
final PsiType psiType = type;
type = ourGuard.doPreventingRecursion(this, true, new Computable<PsiType>() {
@Override
public PsiType compute() {
return resolveResult.getSubstitutor().substitute(psiType);
}
});
}
}
}
}
}
else if (parent instanceof PsiReturnStatement) {
final PsiMethod method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
if (method != null) {
type = method.getReturnType();
}
}
else if (parent instanceof PsiLambdaExpression) {
final PsiType parentInterfaceType = ((PsiLambdaExpression)parent).getFunctionalInterfaceType();
if (parentInterfaceType != null) {
type = LambdaUtil.getFunctionalInterfaceReturnType(parentInterfaceType);
}
}
return type;
}
public int getLambdaIdx(PsiExpressionList expressionList) {
PsiExpression[] expressions = expressionList.getExpressions();
for (int i = 0; i < expressions.length; i++) {
PsiExpression expression = expressions[i];
if (PsiTreeUtil.isAncestor(expression, this, false)) {
return i;
}
}
return -1;
}
@Override
public PsiType getType() {
return new PsiLambdaExpressionType(this);
@@ -12,6 +12,6 @@ class Ambiguity1 {
static <T> void m(I2<T> i2) {}
{
m<error descr="Ambiguous method call: both 'Ambiguity1.m(I1)' and 'Ambiguity1.m(I2<Object>)' match">(()->{throw new AssertionError();})</error>;
m<error descr="Ambiguous method call: both 'Ambiguity1.m(I1)' and 'Ambiguity1.m(I2<T>)' match">(()->{throw new AssertionError();})</error>;
}
}
@@ -11,19 +11,21 @@ interface SameArgsI<T> {
class InferenceFromArgs {
private static <E> void bar(A<? extends E> a, I<? super E, Integer> i) { }
private static <E> void bar(A<E> a, I<E, Integer> i) { }
private static <E> void bazz(I<? super E, Integer> i) { }
void foo(B<Integer> b) {
bar(b, (k, v) -> {int i = k; int j = v; return Math.max(i, j);});
bar(null, <error descr="Cyclic inference">(k, v) -> v</error>);
bar(null, null);
bar(b, (k, v) -> {return v;});
bar(b, (k, v) -> {<error descr="Incompatible types. Found: 'java.lang.Integer', required: 'java.lang.String'">String i = k;</error> return v;});
bar(b, (k, v) -> {Integer i = k; return v;});
bazz(<error descr="Cyclic inference">(k, v) -> v</error>);
bazz((k, v) -> {<error descr="Incompatible types. Found: 'E', required: 'int'">int i = k;</error> return v;});
}
public static <T> SameArgsI<T> max() {
return (a, b) -> b;
}
}
@@ -0,0 +1,17 @@
import java.util.*;
interface In<X> {
List<X> f();
}
class InferenceOnMethodCallSite {
<Z> void m(In<Z> i, In<Z> ii) { }
<Z> void m(In<Z> s) { }
{
m(<error descr="Cyclic inference">( ) -> Collections.emptyList()</error>);
m((In<String>)() -> Collections.emptyList(), () -> new ArrayList<String>());
m(() ->Collections.<String>emptyList(), () -> new ArrayList<String>());
m(() -> Collections.<String>emptyList());
}
}
@@ -6,8 +6,8 @@ class Test {
void foo(Object x) {}
void bar() {
foo(<error descr="Operator '!' cannot be applied to '<lambda expression>'">!(int x)-> {}</error>);
foo(<error descr="Lambda expression is not expected here">(int x)-> { } instanceof Object</error> );
foo(!<error descr="Lambda expression not expected here">(int x)-> {}</error>);
foo(<error descr="Lambda expression not expected here">(int x)-> { }</error> instanceof Object );
}
I bazz() {
@@ -7,11 +7,11 @@ class NoInferenceResult {
<T> void m1(T t) { }
void test() {
m<error descr="'m(I<java.lang.Object,java.lang.Object>)' in 'NoInferenceResult' cannot be applied to '(<lambda expression>)'">((String s1) -> (String s2) -> s1 + s2)</error>;
m((String s1) -> <error descr="Cyclic inference">(String s2) -> s1 + s2</error>);
m((String s1) -> s1.length());
m((String s1) -> s1);
m1<error descr="'m1(java.lang.Object)' in 'NoInferenceResult' cannot be applied to '(<lambda expression>)'">(() -> { })</error>;
m1(<error descr="Cyclic inference">() -> { }</error>);
}
}
@@ -57,7 +57,7 @@ class ReturnTypeCompatibility {
}
public static void main(String[] args) {
<error descr="Cannot resolve method 'call(<lambda expression>)'">call</error>(i-> {return i;});
call(<error descr="Cyclic inference">i-> {return i;}</error>);
}
}
@@ -12,6 +12,6 @@ class TypeArgsConsistency {
I<Integer> i1 = (i, j) -> i + j;
foo((i, j) -> i + j);
I<Integer> i2 = bar((i, j) -> i + j);
I<Integer> i3 = bar<error descr="'bar(TypeArgsConsistency.I<java.lang.Integer>)' in 'TypeArgsConsistency' cannot be applied to '(<lambda expression>)'">((i, j) -> "" + i + j)</error>;
<error descr="Incompatible types. Found: 'TypeArgsConsistency.I<java.lang.String>', required: 'TypeArgsConsistency.I<java.lang.Integer>'">I<Integer> i3 = bar((i, j) -> "" + i + j);</error>
}
}
@@ -0,0 +1,48 @@
public class NotAFIT {
static class First {
static interface A<T> {
void foo1();
void foo2();
}
static <T> void foo(A<T> a) {
}
void bar() {
foo(<error descr="Multiple non-overriding abstract methods found">() ->{}</error>);
}
}
static class WithInheritance {
static interface A<T> {
void foo1();
}
static interface B<M> extends A<M> {
void foo2();
}
static <T> void foo(B<T> a) {
}
void bar() {
foo(<error descr="Multiple non-overriding abstract methods found">()->{}</error>);
}
}
static class WithInheritanceOverrideSameMethod {
static interface A<T> {
void foo1();
}
static interface B<M> extends A<M> {
void foo1();
}
static <T> void foo(B<T> a) {
}
void bar() {
foo(()->{});
}
}
}
@@ -0,0 +1,20 @@
class NoLambda {
interface I<T> {
void f(T t);
}
<Z> void bar(I<Z> iz) {
}
void bazz() {
bar(null);
bar(<error descr="Cyclic inference">(z)-> {System.out.println();}</error>);
}
static <T> T id(T i2) {return i2;}
{
id(<error descr="Cyclic inference">() -> {System.out.println("hi");}</error>);
NoLambda.<Runnable>id(() -> {System.out.println("hi");});
}
}
@@ -30,6 +30,6 @@ class ReturnTypeCompatibility {
public static void main(String[] args) {
call((String i)->{ return i;});
call<error descr="'call(ReturnTypeCompatibility.I1<java.lang.Object>)' in 'ReturnTypeCompatibility' cannot be applied to '(<lambda expression>)'">((int i)->{ return i;})</error>;
call<error descr="'call(ReturnTypeCompatibility.I1<java.lang.Integer>)' in 'ReturnTypeCompatibility' cannot be applied to '(<lambda expression>)'">((int i)->{ return i;})</error>;
}
}
@@ -4,7 +4,7 @@ interface SAM<X> {
class Foo {
void test() {
SAM<Integer> c = (i, j)->"" + i + j;
SAM<String> c = (i, j)->"" + i + j;
SAM<Integer> s3 = m(c);
}
<X> SAM<X> m(SAM<X> s) { return null; }
@@ -1,88 +0,0 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon;
import com.intellij.psi.LambdaUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
public class FunctionalInterfaceTest extends LightDaemonAnalyzerTestCase {
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/functionalInterface";
private void doTestFunctionalInterface(@Nullable String expectedErrorMessage) throws Exception {
String filePath = BASE_PATH + "/" + getTestName(false) + ".java";
configureByFile(filePath);
final PsiClass psiClass = getJavaFacade().findClass("Foo", GlobalSearchScope.projectScope(getProject()));
assertNotNull("Class Foo not found", psiClass);
final String errorMessage = LambdaUtil.checkInterfaceFunctional(psiClass);
assertEquals(expectedErrorMessage, errorMessage);
}
public void testSimple() throws Exception {
doTestFunctionalInterface(null);
}
public void testNoMethods() throws Exception {
doTestFunctionalInterface("No target method found");
}
public void testMultipleMethods() throws Exception {
doTestFunctionalInterface(null);
}
public void testMultipleMethodsInOne() throws Exception {
doTestFunctionalInterface(null);
}
public void testClone() throws Exception {
doTestFunctionalInterface("Multiple non-overriding abstract methods found");
}
public void testTwoMethodsSameSignature() throws Exception {
doTestFunctionalInterface(null);
}
public void testTwoMethodsSubSignature() throws Exception {
doTestFunctionalInterface(null);
}
public void testTwoMethodsNoSubSignature() throws Exception {
doTestFunctionalInterface("Multiple non-overriding abstract methods found");
}
public void testTwoMethodsNoSubSignature1() throws Exception {
doTestFunctionalInterface("Multiple non-overriding abstract methods found");
}
public void testTwoMethodsSameSubstSignature() throws Exception {
doTestFunctionalInterface(null);
}
public void testMethodWithTypeParam() throws Exception {
doTestFunctionalInterface(null);
}
public void testTwoMethodsSameSignatureTypeParams() throws Exception {
doTestFunctionalInterface(null);
}
public void testAbstractClass() throws Exception {
doTestFunctionalInterface("Target type of a lambda conversion must be an interface");
}
}
@@ -48,6 +48,10 @@ public class LambdaHighlightingTest extends LightDaemonAnalyzerTestCase {
public void testWildcardBounds() throws Exception {
doTest();
}
public void testInferenceOnMethodCallSite() throws Exception {
doTest();
}
public void testAmbiguity1() throws Exception {
doTest();
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.lambda;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import org.jetbrains.annotations.NonNls;
public class LambdaInferenceTest extends LightDaemonAnalyzerTestCase {
@NonNls static final String BASE_PATH = "/codeInsight/daemonCodeAnalyzer/lambda/inference";
public void testFunctionalInterfaceShouldHaveExactlyOneMethod() throws Exception {
doTest();
}
public void testSimpleCyclicInference() throws Exception {
doTest();
}
private void doTest() throws Exception {
doTest(BASE_PATH + "/" + getTestName(false) + ".java", false, false);
}
}
@@ -364,7 +364,7 @@ public class IntroduceVariableTest extends LightCodeInsightTestCase {
}
public void testLambdaExprNotAccepted() throws Exception {
doTest(new MockIntroduceVariableHandler("c", false, false, false, "SAM<java.lang.Integer>"));
doTest(new MockIntroduceVariableHandler("c", false, false, false, "SAM<java.lang.String>"));
}
private void doTest(IntroduceVariableBase testMe) throws Exception {