new overload resolution: integrate isPotentiallyCompatible in isApplicable checks
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / tree / java / PsiLambdaExpressionImpl.java
1 /*
2  * Copyright 2000-2012 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.impl.source.tree.java;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.psi.*;
21 import com.intellij.psi.controlFlow.*;
22 import com.intellij.psi.impl.PsiImplUtil;
23 import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
24 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
25 import com.intellij.psi.impl.source.tree.ChildRole;
26 import com.intellij.psi.impl.source.tree.JavaElementType;
27 import com.intellij.psi.infos.MethodCandidateInfo;
28 import com.intellij.psi.scope.PsiScopeProcessor;
29 import com.intellij.psi.tree.IElementType;
30 import com.intellij.psi.util.PsiTreeUtil;
31 import com.intellij.psi.util.PsiUtil;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34
35 import javax.swing.*;
36 import java.util.HashMap;
37 import java.util.Map;
38
39 public class PsiLambdaExpressionImpl extends ExpressionPsiElement implements PsiLambdaExpression {
40
41   private static final ControlFlowPolicy ourPolicy = new ControlFlowPolicy() {
42     @Nullable
43     @Override
44     public PsiVariable getUsedVariable(@NotNull PsiReferenceExpression refExpr) {
45       return null;
46     }
47
48     @Override
49     public boolean isParameterAccepted(@NotNull PsiParameter psiParameter) {
50       return true;
51     }
52
53     @Override
54     public boolean isLocalVariableAccepted(@NotNull PsiLocalVariable psiVariable) {
55       return true;
56     }
57   };
58
59   public PsiLambdaExpressionImpl() {
60     super(JavaElementType.LAMBDA_EXPRESSION);
61   }
62
63   @NotNull
64   @Override
65   public PsiParameterList getParameterList() {
66     return PsiTreeUtil.getRequiredChildOfType(this, PsiParameterList.class);
67   }
68
69   @Override
70   public int getChildRole(ASTNode child) {
71     final IElementType elType = child.getElementType();
72     if (elType == JavaTokenType.ARROW) {
73       return ChildRole.ARROW;
74     } else if (elType == JavaElementType.PARAMETER_LIST) {
75       return ChildRole.PARAMETER_LIST;
76     } else if (elType == JavaElementType.CODE_BLOCK) {
77       return ChildRole.LBRACE;
78     } else {
79       return ChildRole.EXPRESSION;
80     }
81   }
82
83   @Override
84   public PsiElement getBody() {
85     final PsiElement element = getLastChild();
86     return element instanceof PsiExpression || element instanceof PsiCodeBlock ? element : null;
87   }
88
89
90   @Nullable
91   @Override
92   public PsiType getFunctionalInterfaceType() {
93     return FunctionalInterfaceParameterizationUtil.getGroundTargetType(LambdaUtil.getFunctionalInterfaceType(this, true), this);
94   }
95
96   @Override
97   public boolean isVoidCompatible() {
98     final PsiElement body = getBody();
99     if (body instanceof PsiCodeBlock) {
100       for (PsiReturnStatement statement : PsiUtil.findReturnStatements((PsiCodeBlock)body)) {
101         if (statement.getReturnValue() != null) {
102           return false;
103         }
104       }
105     }
106     return true;
107   }
108
109   @Override
110   public boolean isValueCompatible() {
111     final PsiElement body = getBody();
112     if (body instanceof PsiCodeBlock) {
113       try {
114         ControlFlow controlFlow = ControlFlowFactory.getInstance(getProject()).getControlFlow(body, ourPolicy);
115         int startOffset = controlFlow.getStartOffset(body);
116         int endOffset = controlFlow.getEndOffset(body);
117         if (startOffset != -1 && endOffset != -1 && ControlFlowUtil.canCompleteNormally(controlFlow, startOffset, endOffset)) {
118           return false;
119         }
120       }
121       catch (AnalysisCanceledException e) {
122         return false;
123       }
124
125       for (PsiReturnStatement statement : PsiUtil.findReturnStatements((PsiCodeBlock)body)) {
126         if (statement.getReturnValue() == null) {
127           return false;
128         }
129       }
130     }
131     return true;
132   }
133
134   @Override
135   public PsiType getType() {
136     return new PsiLambdaExpressionType(this);
137   }
138
139   @Override
140   public void accept(@NotNull final PsiElementVisitor visitor) {
141     if (visitor instanceof JavaElementVisitor) {
142       ((JavaElementVisitor)visitor).visitLambdaExpression(this);
143     }
144     else {
145       visitor.visitElement(this);
146     }
147   }
148
149   @Override
150   public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
151                                      @NotNull final ResolveState state,
152                                      final PsiElement lastParent,
153                                      @NotNull final PsiElement place) {
154     return PsiImplUtil.processDeclarationsInLambda(this, processor, state, lastParent, place);
155   }
156
157   @Override
158   public String toString() {
159     return "PsiLambdaExpression:" + getText();
160   }
161
162   @Override
163   public boolean hasFormalParameterTypes() {
164     final PsiParameter[] parameters = getParameterList().getParameters();
165     for (PsiParameter parameter : parameters) {
166       if (parameter.getTypeElement() == null) return false;
167     }
168     return true;
169   }
170
171   @Override
172   public boolean isAcceptable(PsiType leftType) {
173     if (leftType instanceof PsiIntersectionType) {
174       for (PsiType conjunctType : ((PsiIntersectionType)leftType).getConjuncts()) {
175         if (isAcceptable(conjunctType)) return true;
176       }
177       return false;
178     }
179     final PsiExpressionList argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
180
181     leftType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(leftType, this);
182
183     final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(leftType);
184     final PsiClass psiClass = resolveResult.getElement();
185     if (psiClass instanceof PsiAnonymousClass) {
186       return isAcceptable(((PsiAnonymousClass)psiClass).getBaseClassType());
187     }
188
189     if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
190       final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
191       if (candidateProperties != null) {
192         final PsiMethod method = candidateProperties.getMethod();
193         if (!InferenceSession.isPertinentToApplicability(this, method) && hasFormalParameterTypes()) {
194           return true;
195         }
196
197         if (LambdaUtil.isPotentiallyCompatibleWithTypeParameter(this, argsList, method)) {
198           return true;
199         }
200       }
201     }
202
203     final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
204
205     if (interfaceMethod == null) return false;
206
207     final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, resolveResult);
208
209     assert leftType != null;
210     if (!isPotentiallyCompatible(leftType)) {
211       return false;
212     }
213
214     if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && !hasFormalParameterTypes()) {
215       return true;
216     }
217
218    
219
220     if (hasFormalParameterTypes()) {
221       final PsiParameter[] lambdaParameters = getParameterList().getParameters();
222       final PsiType[] parameterTypes = interfaceMethod.getSignature(substitutor).getParameterTypes();
223       for (int lambdaParamIdx = 0, length = lambdaParameters.length; lambdaParamIdx < length; lambdaParamIdx++) {
224         PsiParameter parameter = lambdaParameters[lambdaParamIdx];
225         final PsiTypeElement typeElement = parameter.getTypeElement();
226         if (typeElement != null) {
227           final PsiType lambdaFormalType = toArray(typeElement.getType());
228           final PsiType methodParameterType = toArray(parameterTypes[lambdaParamIdx]);
229           if (!lambdaFormalType.equals(methodParameterType)) {
230             return false;
231           }
232         }
233       }
234     }
235
236     PsiType methodReturnType = interfaceMethod.getReturnType();
237     if (methodReturnType != null) {
238       Map<PsiElement, PsiType> map = LambdaUtil.ourFunctionTypes.get();
239       if (map == null) {
240         map = new HashMap<PsiElement, PsiType>();
241         LambdaUtil.ourFunctionTypes.set(map);
242       }
243       try {
244         if (map.put(this, leftType) != null) {
245           return false;
246         }
247         return LambdaHighlightingUtil.checkReturnTypeCompatible(this, substitutor.substitute(methodReturnType)) == null;
248       }
249       finally {
250         map.remove(this);
251       }
252     }
253     return true;
254   }
255
256   //A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:
257   //   The arity of the target type's function type is the same as the arity of the lambda expression.
258   //   If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).
259   //   If the target type's function type has a (non-void) return type, then the lambda body is either an expression or a value-compatible block (§15.27.2).
260   private boolean isPotentiallyCompatible(PsiType left) {
261     final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(left);
262     if (interfaceMethod == null) return false;
263
264     if (getParameterList().getParametersCount() != interfaceMethod.getParameterList().getParametersCount()) {
265       return false;
266     }
267     final PsiType methodReturnType = interfaceMethod.getReturnType();
268     final PsiElement body = getBody();
269     if (methodReturnType == PsiType.VOID) {
270       if (body instanceof PsiCodeBlock) {
271         return isVoidCompatible();
272       } else {
273         return LambdaUtil.isExpressionStatementExpression(body);
274       }
275     }
276     else {
277       return body instanceof PsiCodeBlock && isValueCompatible() || body instanceof PsiExpression;
278     }
279   }
280
281   private static PsiType toArray(PsiType paramType) {
282     if (paramType instanceof PsiEllipsisType) {
283       return ((PsiEllipsisType)paramType).toArrayType();
284     }
285     return paramType;
286   }
287
288   @Nullable
289   @Override
290   public Icon getIcon(int flags) {
291     return AllIcons.Nodes.AnonymousClass;
292   }
293 }