reject caching during resolve only inside lambda body (IDEA-138511)
[idea/community.git] / java / java-psi-api / src / com / intellij / psi / LambdaUtil.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.psi;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Comparing;
20 import com.intellij.openapi.util.Computable;
21 import com.intellij.openapi.util.registry.Registry;
22 import com.intellij.pom.java.LanguageLevel;
23 import com.intellij.psi.infos.MethodCandidateInfo;
24 import com.intellij.psi.util.*;
25 import com.intellij.util.IncorrectOperationException;
26 import com.intellij.util.containers.HashMap;
27 import org.jetbrains.annotations.Contract;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.util.*;
32
33 /**
34  * User: anna
35  * Date: 7/17/12
36  */
37 public class LambdaUtil {
38   public static ThreadLocal<Map<PsiElement, PsiType>> ourFunctionTypes = new ThreadLocal<Map<PsiElement, PsiType>>();
39   private static final Logger LOG = Logger.getInstance("#" + LambdaUtil.class.getName());
40
41   @Nullable
42   public static PsiType getFunctionalInterfaceReturnType(PsiFunctionalExpression expr) {
43     return getFunctionalInterfaceReturnType(expr.getFunctionalInterfaceType());
44   }
45
46   @Nullable
47   public static PsiType getFunctionalInterfaceReturnType(@Nullable PsiType functionalInterfaceType) {
48     final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
49     final PsiClass psiClass = resolveResult.getElement();
50     if (psiClass != null) {
51       final MethodSignature methodSignature = getFunction(psiClass);
52       if (methodSignature != null) {
53         final PsiType returnType = getReturnType(psiClass, methodSignature);
54         return resolveResult.getSubstitutor().substitute(returnType);
55       }
56     }
57     return null;
58   }
59
60   @Nullable
61   public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiType functionalInterfaceType) {
62     return getFunctionalInterfaceMethod(PsiUtil.resolveGenericsClassInType(functionalInterfaceType));
63   }
64
65   public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiElement element) {
66     if (element instanceof PsiFunctionalExpression) {
67       final PsiType samType = ((PsiFunctionalExpression)element).getFunctionalInterfaceType();
68       return getFunctionalInterfaceMethod(samType);
69     }
70     return null;
71   }
72
73   @Nullable
74   public static PsiMethod getFunctionalInterfaceMethod(PsiClassType.ClassResolveResult result) {
75     return getFunctionalInterfaceMethod(result.getElement());
76   }
77
78   @Nullable
79   public static PsiMethod getFunctionalInterfaceMethod(PsiClass aClass) {
80     final MethodSignature methodSignature = getFunction(aClass);
81     if (methodSignature != null) {
82       return getMethod(aClass, methodSignature);
83     }
84     return null;
85   }
86
87   public static PsiSubstitutor getSubstitutor(@NotNull PsiMethod method, @NotNull PsiClassType.ClassResolveResult resolveResult) {
88     final PsiClass derivedClass = resolveResult.getElement();
89     LOG.assertTrue(derivedClass != null);
90
91     final PsiClass methodContainingClass = method.getContainingClass();
92     LOG.assertTrue(methodContainingClass != null);
93     PsiSubstitutor initialSubst = resolveResult.getSubstitutor();
94     final PsiSubstitutor superClassSubstitutor =
95       TypeConversionUtil.getSuperClassSubstitutor(methodContainingClass, derivedClass, PsiSubstitutor.EMPTY);
96     for (PsiTypeParameter param : superClassSubstitutor.getSubstitutionMap().keySet()) {
97       final PsiType substitute = superClassSubstitutor.substitute(param);
98       if (substitute != null) {
99         initialSubst = initialSubst.put(param, initialSubst.substitute(substitute));
100       }
101     }
102     return initialSubst;
103   }
104
105   public static boolean isFunctionalType(PsiType type) {
106     if (type instanceof PsiIntersectionType) {
107       return extractFunctionalConjunct((PsiIntersectionType)type) != null;
108     }
109     return isFunctionalClass(PsiUtil.resolveGenericsClassInType(type).getElement());
110   }
111
112   @Contract("null -> false")
113   public static boolean isFunctionalClass(PsiClass aClass) {
114     if (aClass != null) {
115       if (aClass instanceof PsiTypeParameter) return false;
116       final List<HierarchicalMethodSignature> signatures = findFunctionCandidates(aClass);
117       return signatures != null && signatures.size() == 1;
118     }
119     return false;
120   }
121
122   public static boolean isValidLambdaContext(@Nullable PsiElement context) {
123     return context instanceof PsiTypeCastExpression ||
124            context instanceof PsiAssignmentExpression ||
125            context instanceof PsiVariable ||
126            context instanceof PsiLambdaExpression ||
127            context instanceof PsiReturnStatement ||
128            context instanceof PsiExpressionList ||
129            context instanceof PsiParenthesizedExpression ||
130            context instanceof PsiArrayInitializerExpression ||
131            context instanceof PsiConditionalExpression && PsiTreeUtil.getParentOfType(context, PsiTypeCastExpression.class) == null;
132   }
133
134   public static boolean isLambdaFullyInferred(PsiLambdaExpression expression, PsiType functionalInterfaceType) {
135     final boolean hasParams = expression.getParameterList().getParametersCount() > 0;
136     if (hasParams || getFunctionalInterfaceReturnType(functionalInterfaceType) != PsiType.VOID) {   //todo check that void lambdas without params check
137       
138       return !dependsOnTypeParams(functionalInterfaceType, functionalInterfaceType, expression);
139     }
140     return true;
141   }
142
143   @Nullable
144   public static MethodSignature getFunction(PsiClass psiClass) {
145     if (psiClass == null) return null;
146     final List<HierarchicalMethodSignature> functions = findFunctionCandidates(psiClass);
147     if (functions != null && functions.size() == 1) {
148       return functions.get(0);
149     }
150     return null;
151   }
152
153
154   private static boolean overridesPublicObjectMethod(HierarchicalMethodSignature psiMethod) {
155     final List<HierarchicalMethodSignature> signatures = psiMethod.getSuperSignatures();
156     if (signatures.isEmpty()) {
157       final PsiMethod method = psiMethod.getMethod();
158       final PsiClass containingClass = method.getContainingClass();
159       if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
160         if (method.hasModifierProperty(PsiModifier.PUBLIC)) {
161           return true;
162         }
163       }
164     }
165     for (HierarchicalMethodSignature superMethod : signatures) {
166       if (overridesPublicObjectMethod(superMethod)) {
167         return true;
168       }
169     }
170     return false;
171   }
172
173   private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) {
174     final MethodSignature methodSignature;
175     if (containingClass != null && containingClass != psiClass) {
176       methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY));
177     }
178     else {
179       methodSignature = method.getSignature(PsiSubstitutor.EMPTY);
180     }
181     return methodSignature;
182   }
183
184   @Nullable
185   private static List<HierarchicalMethodSignature> hasSubsignature(List<HierarchicalMethodSignature> signatures) {
186     for (HierarchicalMethodSignature signature : signatures) {
187       boolean subsignature = true;
188       for (HierarchicalMethodSignature methodSignature : signatures) {
189         if (!signature.equals(methodSignature)) {
190           if (!MethodSignatureUtil.isSubsignature(signature, methodSignature) &&
191               !skipMethod(signature, methodSignature)) {
192             subsignature = false;
193             break;
194           }
195         }
196       }
197       if (subsignature) return Collections.singletonList(signature);
198     }
199     return signatures;
200   }
201
202   private static boolean skipMethod(HierarchicalMethodSignature signature,
203                                     HierarchicalMethodSignature methodSignature) {
204     //not generic
205     if (methodSignature.getTypeParameters().length == 0) {
206       return false;
207     }
208     //foreign class
209     return signature.getMethod().getContainingClass() != methodSignature.getMethod().getContainingClass();
210   }
211
212   @Nullable
213   public static List<HierarchicalMethodSignature> findFunctionCandidates(PsiClass psiClass) {
214     if (psiClass != null && psiClass.isInterface() && !psiClass.isAnnotationType()) {
215       final List<HierarchicalMethodSignature> methods = new ArrayList<HierarchicalMethodSignature>();
216       final Collection<HierarchicalMethodSignature> visibleSignatures = psiClass.getVisibleSignatures();
217       for (HierarchicalMethodSignature signature : visibleSignatures) {
218         final PsiMethod psiMethod = signature.getMethod();
219         if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
220         if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) continue;
221         if (!overridesPublicObjectMethod(signature)) {
222           methods.add(signature);
223         }
224       }
225
226       return hasSubsignature(methods);
227     }
228     return null;
229   }
230
231
232   @Nullable
233   private static PsiType getReturnType(PsiClass psiClass, MethodSignature methodSignature) {
234     final PsiMethod method = getMethod(psiClass, methodSignature);
235     if (method != null) {
236       final PsiClass containingClass = method.getContainingClass();
237       if (containingClass == null) return null;
238       return TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY).substitute(method.getReturnType());
239     }
240     else {
241       return null;
242     }
243   }
244
245   @Nullable
246   private static PsiMethod getMethod(PsiClass psiClass, MethodSignature methodSignature) {
247     if (methodSignature instanceof MethodSignatureBackedByPsiMethod) {
248       return ((MethodSignatureBackedByPsiMethod)methodSignature).getMethod();
249     }
250
251     final PsiMethod[] methodsByName = psiClass.findMethodsByName(methodSignature.getName(), true);
252     for (PsiMethod psiMethod : methodsByName) {
253       if (MethodSignatureUtil
254         .areSignaturesEqual(getMethodSignature(psiMethod, psiClass, psiMethod.getContainingClass()), methodSignature)) {
255         return psiMethod;
256       }
257     }
258     return null;
259   }
260
261   public static int getLambdaIdx(PsiExpressionList expressionList, final PsiElement element) {
262     PsiExpression[] expressions = expressionList.getExpressions();
263     for (int i = 0; i < expressions.length; i++) {
264       PsiExpression expression = expressions[i];
265       if (PsiTreeUtil.isAncestor(expression, element, false)) {
266         return i;
267       }
268     }
269     return -1;
270   }
271
272   public static boolean dependsOnTypeParams(PsiType type,
273                                             PsiType functionalInterfaceType,
274                                             PsiElement lambdaExpression,
275                                             PsiTypeParameter... param2Check) {
276     return depends(type, new TypeParamsChecker(lambdaExpression,
277                                                PsiUtil.resolveClassInType(functionalInterfaceType)), param2Check);
278   }
279
280   public static boolean depends(PsiType type, TypeParamsChecker visitor, PsiTypeParameter... param2Check) {
281     if (!visitor.startedInference()) return false;
282     final Boolean accept = type.accept(visitor);
283     if (param2Check.length > 0) {
284       return visitor.used(param2Check);
285     }
286     return accept != null && accept.booleanValue();
287   }
288
289   @Nullable
290   public static PsiType getFunctionalInterfaceType(PsiElement expression, final boolean tryToSubstitute) {
291     PsiElement parent = expression.getParent();
292     PsiElement element = expression;
293     while (parent instanceof PsiParenthesizedExpression || parent instanceof PsiConditionalExpression) {
294       if (parent instanceof PsiConditionalExpression &&
295           ((PsiConditionalExpression)parent).getThenExpression() != element &&
296           ((PsiConditionalExpression)parent).getElseExpression() != element) break;
297       element = parent;
298       parent = parent.getParent();
299     }
300
301     final Map<PsiElement, PsiType> map = ourFunctionTypes.get();
302     if (map != null) {
303       final PsiType type = map.get(expression);
304       if (type != null) {
305         return type;
306       }
307     }
308
309     if (parent instanceof PsiArrayInitializerExpression) {
310       final PsiType psiType = ((PsiArrayInitializerExpression)parent).getType();
311       if (psiType instanceof PsiArrayType) {
312         return ((PsiArrayType)psiType).getComponentType();
313       }
314     } else if (parent instanceof PsiTypeCastExpression) {
315       final PsiType castType = ((PsiTypeCastExpression)parent).getType();
316       if (castType instanceof PsiIntersectionType) {
317         final PsiType conjunct = extractFunctionalConjunct((PsiIntersectionType)castType);
318         if (conjunct != null) return conjunct;
319       }
320       return castType;
321     }
322     else if (parent instanceof PsiVariable) {
323       return ((PsiVariable)parent).getType();
324     }
325     else if (parent instanceof PsiAssignmentExpression && expression instanceof PsiExpression && !PsiUtil.isOnAssignmentLeftHand((PsiExpression)expression)) {
326       final PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression();
327       return lExpression.getType();
328     }
329     else if (parent instanceof PsiExpressionList) {
330       final PsiExpressionList expressionList = (PsiExpressionList)parent;
331       final int lambdaIdx = getLambdaIdx(expressionList, expression);
332       if (lambdaIdx > -1) {
333
334         PsiElement gParent = expressionList.getParent();
335
336         if (gParent instanceof PsiAnonymousClass) {
337           gParent = gParent.getParent();
338         }
339
340         if (gParent instanceof PsiCall) {
341           final PsiCall contextCall = (PsiCall)gParent;
342           final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(contextCall.getArgumentList());
343           if (MethodCandidateInfo.isOverloadCheck()) {
344             MethodCandidateInfo.ourOverloadGuard.prohibitResultCaching(MethodCandidateInfo.ourOverloadGuard.currentStack().get(0));
345           }
346           if (properties != null && properties.isApplicabilityCheck()) { //todo simplification
347             final PsiParameter[] parameters = properties.getMethod().getParameterList().getParameters();
348             final int finalLambdaIdx = adjustLambdaIdx(lambdaIdx, properties.getMethod(), parameters);
349             if (finalLambdaIdx < parameters.length) {
350               return properties.getSubstitutor().substitute(getNormalizedType(parameters[finalLambdaIdx]));
351             }
352           }
353           final JavaResolveResult resolveResult = contextCall.resolveMethodGenerics();
354             final PsiElement resolve = resolveResult.getElement();
355             if (resolve instanceof PsiMethod) {
356               final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters();
357               final int finalLambdaIdx = adjustLambdaIdx(lambdaIdx, (PsiMethod)resolve, parameters);
358               if (finalLambdaIdx < parameters.length) {
359                 if (!tryToSubstitute) return getNormalizedType(parameters[finalLambdaIdx]);
360                 return PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, true, new Computable<PsiType>() {
361                   @Override
362                   public PsiType compute() {
363                     return resolveResult.getSubstitutor().substitute(getNormalizedType(parameters[finalLambdaIdx]));
364                   }
365                 });
366               }
367             }
368             return null;
369         }
370       }
371     }
372     else if (parent instanceof PsiReturnStatement) {
373       final PsiElement gParent = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, PsiMethod.class);
374       if (gParent instanceof PsiLambdaExpression) {
375         return getFunctionalInterfaceTypeByContainingLambda((PsiLambdaExpression)gParent);
376       } else if (gParent instanceof PsiMethod) {
377         return ((PsiMethod)gParent).getReturnType();
378       }
379     }
380     else if (parent instanceof PsiLambdaExpression) {
381       return getFunctionalInterfaceTypeByContainingLambda((PsiLambdaExpression)parent);
382     }
383     return null;
384   }
385
386   @Nullable
387   private static PsiType extractFunctionalConjunct(PsiIntersectionType type) {
388     PsiType conjunct = null;
389     for (PsiType conjunctType : type.getConjuncts()) {
390       final PsiMethod interfaceMethod = getFunctionalInterfaceMethod(conjunctType);
391       if (interfaceMethod != null) {
392         if (conjunct != null && !conjunct.equals(conjunctType)) return null;
393         conjunct = conjunctType;
394       }
395     }
396     return conjunct;
397   }
398   
399   private static PsiType getFunctionalInterfaceTypeByContainingLambda(@NotNull PsiLambdaExpression parentLambda) {
400     final PsiType parentInterfaceType = parentLambda.getFunctionalInterfaceType();
401     return parentInterfaceType != null ? getFunctionalInterfaceReturnType(parentInterfaceType) : null;
402   }
403
404   private static int adjustLambdaIdx(int lambdaIdx, PsiMethod resolve, PsiParameter[] parameters) {
405     final int finalLambdaIdx;
406     if (resolve.isVarArgs() && lambdaIdx >= parameters.length) {
407       finalLambdaIdx = parameters.length - 1;
408     } else {
409       finalLambdaIdx = lambdaIdx;
410     }
411     return finalLambdaIdx;
412   }
413
414   private static PsiType getNormalizedType(PsiParameter parameter) {
415     final PsiType type = parameter.getType();
416     if (type instanceof PsiEllipsisType) {
417       return ((PsiEllipsisType)type).getComponentType();
418     }
419     return type;
420   }
421
422   public static boolean notInferredType(PsiType typeByExpression) {
423     return typeByExpression instanceof PsiMethodReferenceType || typeByExpression instanceof PsiLambdaExpressionType || typeByExpression instanceof PsiLambdaParameterType;
424   }
425
426   public static PsiReturnStatement[] getReturnStatements(PsiLambdaExpression lambdaExpression) {
427     final PsiElement body = lambdaExpression.getBody();
428     return body instanceof PsiCodeBlock ? PsiUtil.findReturnStatements((PsiCodeBlock)body) : PsiReturnStatement.EMPTY_ARRAY;
429   }
430
431   public static List<PsiExpression> getReturnExpressions(PsiLambdaExpression lambdaExpression) {
432     final PsiElement body = lambdaExpression.getBody();
433     if (body instanceof PsiExpression) {
434       //if (((PsiExpression)body).getType() != PsiType.VOID) return Collections.emptyList();
435       return Collections.singletonList((PsiExpression)body);
436     }
437     final List<PsiExpression> result = new ArrayList<PsiExpression>();
438     for (PsiReturnStatement returnStatement : getReturnStatements(lambdaExpression)) {
439       final PsiExpression returnValue = returnStatement.getReturnValue();
440       if (returnValue != null) {
441         result.add(returnValue);
442       }
443     }
444     return result;
445   }
446
447   public static boolean isValidQualifier4InterfaceStaticMethodCall(@NotNull PsiMethod method,
448                                                                    @NotNull PsiReferenceExpression methodReferenceExpression,
449                                                                    @Nullable PsiElement scope, @NotNull LanguageLevel languageLevel) {
450     return getInvalidQualifier4StaticInterfaceMethodMessage(method, methodReferenceExpression, scope, languageLevel) == null;
451   }
452
453   @Nullable
454   public static String getInvalidQualifier4StaticInterfaceMethodMessage(@NotNull PsiMethod method,
455                                                                         @NotNull PsiReferenceExpression methodReferenceExpression,
456                                                                         @Nullable PsiElement scope, @NotNull LanguageLevel languageLevel) {
457     final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression();
458     final PsiClass containingClass = method.getContainingClass();
459     if (containingClass != null && containingClass.isInterface() && method.hasModifierProperty(PsiModifier.STATIC)) {
460       if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
461         return "Static interface method invocations are not supported at this language level";
462       }
463
464       if (qualifierExpression == null && 
465           (scope instanceof PsiImportStaticStatement || PsiTreeUtil.isAncestor(containingClass, methodReferenceExpression, true)) || 
466           qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).resolve() == containingClass) {
467         return null;
468       }
469       return "Static method may be invoked on containing interface class only";
470     }
471     return null;
472   }
473
474   //JLS 14.8 Expression Statements
475   public static boolean isExpressionStatementExpression(PsiElement body) {
476     return body instanceof PsiAssignmentExpression ||
477            body instanceof PsiPrefixExpression &&
478            (((PsiPrefixExpression)body).getOperationTokenType() == JavaTokenType.PLUSPLUS ||
479             ((PsiPrefixExpression)body).getOperationTokenType() == JavaTokenType.MINUSMINUS) ||
480            body instanceof PsiPostfixExpression ||
481            body instanceof PsiCallExpression ||
482            body instanceof PsiReferenceExpression && !body.isPhysical();
483   }
484
485   public static PsiExpression extractSingleExpressionFromBody(PsiElement body) {
486     PsiExpression expression = null;
487     if (body instanceof PsiExpression) {
488       expression = (PsiExpression)body;
489     }
490     else if (body instanceof PsiCodeBlock) {
491       final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements();
492       if (statements.length == 1) {
493         if (statements[0] instanceof PsiReturnStatement) {
494           expression = ((PsiReturnStatement)statements[0]).getReturnValue();
495         }
496         else if (statements[0] instanceof PsiExpressionStatement) {
497           expression = ((PsiExpressionStatement)statements[0]).getExpression();
498         } else if (statements[0] instanceof PsiBlockStatement) {
499           return extractSingleExpressionFromBody(((PsiBlockStatement)statements[0]).getCodeBlock());
500         }
501       }
502     }
503     else if (body instanceof PsiBlockStatement) {
504       return extractSingleExpressionFromBody(((PsiBlockStatement)body).getCodeBlock());
505     }
506     else if (body instanceof PsiExpressionStatement) {
507       expression = ((PsiExpressionStatement)body).getExpression();
508     }
509     return expression;
510   }
511
512   // http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1
513   // A lambda expression or a method reference expression is potentially compatible with a type variable 
514   // if the type variable is a type parameter of the candidate method.
515   public static boolean isPotentiallyCompatibleWithTypeParameter(PsiFunctionalExpression expression,
516                                                                  PsiExpressionList argsList,
517                                                                  PsiMethod method) {
518     if (!Registry.is("JDK8042508.bug.fixed", false)) {
519       final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(argsList, PsiCallExpression.class);
520       if (callExpression == null || callExpression.getTypeArguments().length > 0) {
521         return false;
522       }
523     }
524
525     final int lambdaIdx = getLambdaIdx(argsList, expression);
526     if (lambdaIdx >= 0) {
527       final PsiParameter[] parameters = method.getParameterList().getParameters();
528       final PsiParameter lambdaParameter = parameters[Math.min(lambdaIdx, parameters.length - 1)];
529       final PsiClass paramClass = PsiUtil.resolveClassInType(lambdaParameter.getType());
530       if (paramClass instanceof PsiTypeParameter && ((PsiTypeParameter)paramClass).getOwner() == method) {
531         return true;
532       }
533     }
534     return false;
535   }
536
537   @NotNull
538   public static Map<PsiElement, PsiType> getFunctionalTypeMap() {
539     Map<PsiElement, PsiType> map = ourFunctionTypes.get();
540     if (map == null) {
541       map = new HashMap<PsiElement, PsiType>();
542       ourFunctionTypes.set(map);
543     }
544     return map;
545   }
546
547   public static String checkReturnTypeCompatible(PsiLambdaExpression lambdaExpression, PsiType functionalInterfaceReturnType) {
548     if (functionalInterfaceReturnType == PsiType.VOID) {
549       final PsiElement body = lambdaExpression.getBody();
550       if (body instanceof PsiCodeBlock) {
551         if (!getReturnExpressions(lambdaExpression).isEmpty()) return "Unexpected return value";
552       } else if (body instanceof PsiExpression) {
553         final PsiType type = ((PsiExpression)body).getType();
554         try {
555           if (!PsiUtil.isStatement(JavaPsiFacade.getElementFactory(body.getProject()).createStatementFromText(body.getText(), body))) {
556             return "Bad return type in lambda expression: " + (type == PsiType.NULL || type == null ? "<null>" : type.getPresentableText()) + " cannot be converted to void";
557           }
558         }
559         catch (IncorrectOperationException ignore) {
560         }
561       }
562     } else if (functionalInterfaceReturnType != null) {
563       final List<PsiExpression> returnExpressions = getReturnExpressions(lambdaExpression);
564       for (final PsiExpression expression : returnExpressions) {
565         final PsiType expressionType = PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, true, new Computable<PsiType>() {
566           @Override
567           public PsiType compute() {
568             return expression.getType();
569           }
570         });
571         if (expressionType != null && !functionalInterfaceReturnType.isAssignableFrom(expressionType)) {
572           return "Bad return type in lambda expression: " + expressionType.getPresentableText() + " cannot be converted to " + functionalInterfaceReturnType.getPresentableText();
573         }
574       }
575       if (getReturnStatements(lambdaExpression).length > returnExpressions.size() || returnExpressions.isEmpty() && !lambdaExpression.isVoidCompatible()) {
576         return "Missing return value";
577       }
578     }
579     return null;
580   }
581
582   public static class TypeParamsChecker extends PsiTypeVisitor<Boolean> {
583     private PsiMethod myMethod;
584     private final PsiClass myClass;
585     public final Set<PsiTypeParameter> myUsedTypeParams = new HashSet<PsiTypeParameter>();
586
587     public TypeParamsChecker(PsiElement expression, PsiClass aClass) {
588       myClass = aClass;
589       PsiElement parent = expression != null ? expression.getParent() : null;
590       while (parent instanceof PsiParenthesizedExpression) {
591         parent = parent.getParent();
592       }
593       if (parent instanceof PsiExpressionList) {
594         final PsiElement gParent = parent.getParent();
595         if (gParent instanceof PsiCall) {
596           final MethodCandidateInfo.CurrentCandidateProperties pair = MethodCandidateInfo.getCurrentMethod(parent);
597           myMethod = pair != null ? pair.getMethod() : null;
598           if (myMethod == null) {
599             myMethod = ((PsiCall)gParent).resolveMethod();
600           }
601           if (myMethod != null && PsiTreeUtil.isAncestor(myMethod, expression, false)) {
602             myMethod = null;
603           }
604         }
605       }
606     }
607
608     public boolean startedInference() {
609       return myMethod != null;
610     }
611
612     @Override
613     public Boolean visitClassType(PsiClassType classType) {
614       boolean used = false;
615       for (PsiType paramType : classType.getParameters()) {
616         final Boolean paramAccepted = paramType.accept(this);
617         used |= paramAccepted != null && paramAccepted.booleanValue();
618       }
619       final PsiClass resolve = classType.resolve();
620       if (resolve instanceof PsiTypeParameter) {
621         final PsiTypeParameter typeParameter = (PsiTypeParameter)resolve;
622         if (check(typeParameter)) {
623           myUsedTypeParams.add(typeParameter);
624           return true;
625         }
626       }
627       return used;
628     }
629
630     @Nullable
631     @Override
632     public Boolean visitWildcardType(PsiWildcardType wildcardType) {
633       final PsiType bound = wildcardType.getBound();
634       if (bound != null) return bound.accept(this);
635       return false;
636     }
637
638     @Nullable
639     @Override
640     public Boolean visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
641       return visitWildcardType(capturedWildcardType.getWildcard());
642     }
643
644     @Nullable
645     @Override
646     public Boolean visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) {
647       return true;
648     }
649
650     @Nullable
651     @Override
652     public Boolean visitArrayType(PsiArrayType arrayType) {
653       return arrayType.getComponentType().accept(this);
654     }
655
656     @Override
657     public Boolean visitType(PsiType type) {
658       return false;
659     }
660
661     private boolean check(PsiTypeParameter check) {
662       final PsiTypeParameterListOwner owner = check.getOwner();
663       if (owner == myMethod) {
664         return true;
665       }
666       else if (owner == myClass) {
667         return true;
668       }
669       return false;
670     }
671
672     public boolean used(PsiTypeParameter... parameters) {
673       for (PsiTypeParameter parameter : parameters) {
674         if (myUsedTypeParams.contains(parameter)) return true;
675       }
676       return false;
677     }
678   }
679 }