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