a9ba1bbaa5b0744f1f8193b84d44a1bd98a0b3f6
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / tree / java / PsiMethodReferenceExpressionImpl.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.impl.source.tree.java;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.lang.ASTNode;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.Comparing;
22 import com.intellij.openapi.util.TextRange;
23 import com.intellij.psi.*;
24 import com.intellij.psi.impl.PsiImplUtil;
25 import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
26 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
27 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
28 import com.intellij.psi.impl.source.tree.ChildRole;
29 import com.intellij.psi.impl.source.tree.JavaElementType;
30 import com.intellij.psi.infos.MethodCandidateInfo;
31 import com.intellij.psi.scope.ElementClassFilter;
32 import com.intellij.psi.scope.PsiConflictResolver;
33 import com.intellij.psi.scope.PsiScopeProcessor;
34 import com.intellij.psi.scope.conflictResolvers.DuplicateConflictResolver;
35 import com.intellij.psi.scope.processor.FilterScopeProcessor;
36 import com.intellij.psi.scope.util.PsiScopesUtil;
37 import com.intellij.psi.tree.IElementType;
38 import com.intellij.psi.util.*;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import javax.swing.*;
45 import java.util.*;
46
47 public class PsiMethodReferenceExpressionImpl extends PsiReferenceExpressionBase implements PsiMethodReferenceExpression {
48   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiMethodReferenceExpressionImpl");
49   private static final MethodReferenceResolver RESOLVER = new MethodReferenceResolver();
50
51   public PsiMethodReferenceExpressionImpl() {
52     super(JavaElementType.METHOD_REF_EXPRESSION);
53   }
54
55
56   @Override
57   public PsiTypeElement getQualifierType() {
58     final PsiElement qualifier = getQualifier();
59     return qualifier instanceof PsiTypeElement ? (PsiTypeElement)qualifier : null;
60   }
61
62   @Nullable
63   @Override
64   public PsiType getFunctionalInterfaceType() {
65     return FunctionalInterfaceParameterizationUtil.getGroundTargetType(LambdaUtil.getFunctionalInterfaceType(this, true));
66   }
67
68   @Override
69   public boolean isExact() {
70     return getPotentiallyApplicableMember() != null;
71   }
72
73   @Override
74   public boolean isPotentiallyCompatible(final PsiType functionalInterfaceType) {
75     final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
76     if (interfaceMethod == null) return false;
77
78     final MethodReferenceResolver resolver = new MethodReferenceResolver() {
79       @Override
80       protected PsiConflictResolver createResolver(PsiMethodReferenceExpressionImpl referenceExpression,
81                                                    PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult,
82                                                    PsiMethod interfaceMethod,
83                                                    MethodSignature signature) {
84         return DuplicateConflictResolver.INSTANCE;
85       }
86
87       @Override
88       protected PsiType getInterfaceType(PsiMethodReferenceExpression reference) {
89         return functionalInterfaceType;
90       }
91     };
92
93     final ResolveResult[] result = resolver.resolve(this, getContainingFile(), false);
94     final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(this);
95     final int interfaceArity = interfaceMethod.getParameterList().getParametersCount();
96     for (ResolveResult resolveResult : result) {
97       final PsiElement element = resolveResult.getElement();
98       if (element instanceof PsiMethod) {
99         final boolean isStatic = ((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC);
100         final int parametersCount = ((PsiMethod)element).getParameterList().getParametersCount();
101         if (qualifierResolveResult.isReferenceTypeQualified() && getReferenceNameElement() instanceof PsiIdentifier) {
102           if (parametersCount == interfaceArity && isStatic) {
103             return true;
104           }
105           if (parametersCount == interfaceArity - 1 && !isStatic) {
106             return true;
107           }
108           if (((PsiMethod)element).isVarArgs()) return true;
109         }
110         else if (!isStatic) {
111           if (parametersCount == interfaceArity || ((PsiMethod)element).isVarArgs()) {
112             return true;
113           }
114         }
115       } else if (element instanceof PsiClass) {
116         return true;
117       }
118     }
119     return false;
120   }
121
122   @Override
123   public PsiMember getPotentiallyApplicableMember() {
124     return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiMember>() {
125       @Nullable
126       @Override
127       public Result<PsiMember> compute() {
128         return Result.createSingleDependency(getPotentiallyApplicableMemberInternal(),
129                                              PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
130       }
131     });
132   }
133
134   private PsiMember getPotentiallyApplicableMemberInternal() {
135     final PsiElement element = getReferenceNameElement();
136     final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(this);
137     final PsiClass containingClass = qualifierResolveResult.getContainingClass();
138     if (containingClass != null) {
139       PsiMethod[] methods = null;
140       if (element instanceof PsiIdentifier) {
141         final String identifierName = element.getText();
142         final List<PsiMethod> result = new ArrayList<PsiMethod>();
143         for (HierarchicalMethodSignature signature : containingClass.getVisibleSignatures()) {
144           if (identifierName.equals(signature.getName())) {
145             result.add(signature.getMethod());
146           }
147         }
148         methods = result.toArray(new PsiMethod[result.size()]);
149       }
150       else if (isConstructor()) {
151         final PsiElementFactory factory = JavaPsiFacade.getElementFactory(getProject());
152         final PsiClass arrayClass = factory.getArrayClass(PsiUtil.getLanguageLevel(this));
153         if (arrayClass == containingClass) {
154           final PsiType componentType = qualifierResolveResult.getSubstitutor().substitute(arrayClass.getTypeParameters()[0]);
155           LOG.assertTrue(componentType != null, qualifierResolveResult.getSubstitutor());
156           //15.13.1 A method reference expression of the form ArrayType :: new is always exact.
157           return factory.createMethodFromText("public " + componentType.createArrayType().getCanonicalText() + " __array__(int i) {return null;}", this);
158         } else {
159           methods = containingClass.getConstructors();
160         }
161       }
162       if (methods != null) {
163         PsiMethod psiMethod = null;
164         if (methods.length > 0) {
165           for (PsiMethod method : methods) {
166             if (PsiUtil.isAccessible(method, this, null)) {
167               if (psiMethod != null) return null;
168               psiMethod = method;
169             }
170           }
171           if (psiMethod == null) return null;
172           if (psiMethod.isVarArgs()) return null;
173           if (psiMethod.getTypeParameters().length > 0) {
174             final PsiReferenceParameterList parameterList = getParameterList();
175             return parameterList != null && parameterList.getTypeParameterElements().length > 0 ? psiMethod : null;
176           } else {
177             final PsiSubstitutor classSubstitutor = TypeConversionUtil.getClassSubstitutor(psiMethod.getContainingClass(), containingClass, PsiSubstitutor.EMPTY);
178             final Set<PsiType> signature = new HashSet<PsiType>(Arrays.asList(psiMethod.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()));
179             signature.add(psiMethod.getReturnType());
180             boolean free = true;
181             for (PsiType type : signature) {
182               if (classSubstitutor != null) {
183                 type = classSubstitutor.substitute(type);
184               }
185               if (type != null && PsiPolyExpressionUtil.mentionsTypeParameters(type, ContainerUtil.newHashSet(containingClass.getTypeParameters()))) {
186                 free = false;
187                 break;
188               }
189             }
190             if (free) return psiMethod;
191           }
192         }
193         if (containingClass.hasTypeParameters()) {
194           final PsiElement qualifier = getQualifier();
195           PsiJavaCodeReferenceElement referenceElement = null;
196           if (qualifier instanceof PsiTypeElement) {
197             referenceElement = ((PsiTypeElement)qualifier).getInnermostComponentReferenceElement();
198           } else if (qualifier instanceof PsiReferenceExpression) {
199             final PsiReferenceExpression expression = (PsiReferenceExpression)qualifier;
200             if (qualifierResolveResult.isReferenceTypeQualified()) {
201               referenceElement = expression;
202             }
203           }
204           if (referenceElement != null) {
205             final PsiReferenceParameterList parameterList = referenceElement.getParameterList();
206             if (parameterList == null || parameterList.getTypeParameterElements().length == 0) {
207               return null;
208             }
209           }
210         }
211         return psiMethod == null ? containingClass : psiMethod;
212       }
213     }
214     return null;
215   }
216   
217   @Override
218   public PsiExpression getQualifierExpression() {
219     final PsiElement qualifier = getQualifier();
220     return qualifier instanceof PsiExpression ? (PsiExpression)qualifier : null;
221   }
222
223   @Override
224   public PsiType getType() {
225     return new PsiMethodReferenceType(this);
226   }
227
228   @Override
229   public PsiElement getReferenceNameElement() {
230     final PsiElement element = getLastChild();
231     return element instanceof PsiIdentifier || PsiUtil.isJavaToken(element, JavaTokenType.NEW_KEYWORD) ? element : null;
232   }
233
234   @Override
235   public void processVariants(@NotNull final PsiScopeProcessor processor) {
236     final FilterScopeProcessor proc = new FilterScopeProcessor(ElementClassFilter.METHOD, processor);
237     PsiScopesUtil.resolveAndWalk(proc, this, null, true);
238   }
239
240   @Override
241   public void setQualifierExpression(@Nullable PsiExpression newQualifier) throws IncorrectOperationException {
242     if (newQualifier == null) {
243       LOG.error("Forbidden null qualifier");
244       return;
245     }
246     final PsiExpression expression = getQualifierExpression();
247     if (expression != null) {
248       expression.replace(newQualifier);
249     } else {
250       final PsiElement qualifier = getQualifier();
251       if (qualifier != null) {
252         qualifier.replace(newQualifier);
253       }
254     }
255   }
256
257   @Override
258   public int getChildRole(ASTNode child) {
259     final IElementType elType = child.getElementType();
260     if (elType == JavaTokenType.DOUBLE_COLON) {
261       return ChildRole.DOUBLE_COLON;
262     } else if (elType == JavaTokenType.IDENTIFIER) {
263       return ChildRole.REFERENCE_NAME;
264     } else if (elType == JavaElementType.REFERENCE_EXPRESSION) {
265       return ChildRole.CLASS_REFERENCE;
266     }
267     return ChildRole.EXPRESSION;
268   }
269
270   @NotNull
271   @Override
272   public JavaResolveResult[] multiResolve(boolean incompleteCode) {
273     return PsiImplUtil.multiResolveImpl(this, incompleteCode, RESOLVER);
274   }
275
276   @Override
277   public PsiElement getQualifier() {
278     final PsiElement element = getFirstChild();
279     return element instanceof PsiExpression || element instanceof PsiTypeElement ? element : null;
280   }
281
282   @Override
283   public TextRange getRangeInElement() {
284     final PsiElement element = getReferenceNameElement();
285     if (element != null) {
286       final int offsetInParent = element.getStartOffsetInParent();
287       return new TextRange(offsetInParent, offsetInParent + element.getTextLength());
288     }
289     final PsiElement colons = findPsiChildByType(JavaTokenType.DOUBLE_COLON);
290     if (colons != null) {
291       final int offsetInParent = colons.getStartOffsetInParent();
292       return new TextRange(offsetInParent, offsetInParent + colons.getTextLength());
293     }
294     LOG.error(getText());
295     return null;
296   }
297
298   @NotNull
299   @Override
300   public String getCanonicalText() {
301     return getText();
302   }
303
304   @Override
305   public boolean isReferenceTo(final PsiElement element) {
306     if (!(element instanceof PsiMethod)) return false;
307     final PsiMethod method = (PsiMethod)element;
308
309     final PsiElement nameElement = getReferenceNameElement();
310     if (nameElement instanceof PsiIdentifier) {
311       if (!nameElement.getText().equals(method.getName())) return false;
312     }
313     else if (PsiUtil.isJavaToken(nameElement, JavaTokenType.NEW_KEYWORD)) {
314       if (!method.isConstructor()) return false;
315     }
316
317     return element.getManager().areElementsEquivalent(element, resolve());
318   }
319
320   @Override
321   public void accept(@NotNull final PsiElementVisitor visitor) {
322     if (visitor instanceof JavaElementVisitor) {
323       ((JavaElementVisitor)visitor).visitMethodReferenceExpression(this);
324     }
325     else {
326       visitor.visitElement(this);
327     }
328   }
329
330   @Override
331   public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
332     return this;
333   }
334
335   @Override
336   public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
337     PsiElement oldIdentifier = findChildByRoleAsPsiElement(ChildRole.REFERENCE_NAME);
338     if (oldIdentifier == null) {
339       oldIdentifier = findChildByRoleAsPsiElement(ChildRole.CLASS_REFERENCE);
340     }
341     if (oldIdentifier == null) {
342       throw new IncorrectOperationException();
343     }
344     final String oldRefName = oldIdentifier.getText();
345     if (PsiKeyword.THIS.equals(oldRefName) ||
346         PsiKeyword.SUPER.equals(oldRefName) ||
347         PsiKeyword.NEW.equals(oldRefName) ||
348         Comparing.strEqual(oldRefName, newElementName)) {
349       return this;
350     }
351     PsiIdentifier identifier = JavaPsiFacade.getInstance(getProject()).getElementFactory().createIdentifier(newElementName);
352     oldIdentifier.replace(identifier);
353     return this;
354   }
355
356   @Override
357   public boolean isConstructor() {
358     final PsiElement element = getReferenceNameElement();
359     return element instanceof PsiKeyword && PsiKeyword.NEW.equals(element.getText());
360   }
361
362   @Override
363   public String toString() {
364     return "PsiMethodReferenceExpression:" + getText();
365   }
366
367   @Override
368   public boolean isAcceptable(PsiType left) {
369     if (left instanceof PsiIntersectionType) {
370       for (PsiType conjunct : ((PsiIntersectionType)left).getConjuncts()) {
371         if (isAcceptable(conjunct)) return true;
372       }
373       return false;
374     }
375
376     final PsiElement argsList = PsiTreeUtil.getParentOfType(this, PsiExpressionList.class);
377     final boolean isExact = isExact();
378     if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList) && isExact) {
379       final MethodCandidateInfo.CurrentCandidateProperties candidateProperties = MethodCandidateInfo.getCurrentMethod(argsList);
380       if (candidateProperties != null && !InferenceSession.isPertinentToApplicability(this, candidateProperties.getMethod())) {
381         return true;
382       }
383     }
384
385     left = FunctionalInterfaceParameterizationUtil.getGroundTargetType(left);
386     if (!isPotentiallyCompatible(left)) {
387       return false;
388     }
389
390     if (MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argsList)) {
391       if (!isExact) {
392         return true;
393       }
394     }
395
396      // A method reference is congruent with a function type if the following are true:
397      //   The function type identifies a single compile-time declaration corresponding to the reference.
398      //   One of the following is true:
399      //      i)The return type of the function type is void.
400      //     ii)The return type of the function type is R; 
401      //        the result of applying capture conversion (5.1.10) to the return type of the invocation type (15.12.2.6) of the chosen declaration is R', 
402      //        where R is the target type that may be used to infer R'; neither R nor R' is void; and R' is compatible with R in an assignment context.
403
404     Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
405     final JavaResolveResult result;
406     try {
407       if (map.put(this, left) != null) {
408         return false;
409       }
410       result = advancedResolve(false);
411     }
412     finally {
413       map.remove(this);
414     }
415
416     final PsiElement resolve = result.getElement();
417     if (resolve == null) {
418       return false;
419     }
420
421     final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(left);
422     final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
423     if (interfaceMethod != null) {
424       final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(left);
425
426       if (interfaceReturnType == PsiType.VOID || interfaceReturnType == null) {
427         return true;
428       }
429
430       PsiSubstitutor subst = result.getSubstitutor();
431
432       PsiType methodReturnType = null;
433       PsiClass containingClass = null;
434       if (resolve instanceof PsiMethod) {
435         containingClass = ((PsiMethod)resolve).getContainingClass();
436
437         PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(this, this, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(this));
438
439         if (returnType == null) {
440           returnType = ((PsiMethod)resolve).getReturnType();
441         }
442
443         if (returnType == PsiType.VOID) {
444           return false;
445         }
446
447         PsiClass qContainingClass = PsiMethodReferenceUtil.getQualifierResolveResult(this).getContainingClass();
448         if (qContainingClass != null && containingClass != null &&
449             PsiMethodReferenceUtil.isReceiverType(left, containingClass, (PsiMethod)resolve)) {
450           subst = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, subst);
451           LOG.assertTrue(subst != null);
452         }
453
454         methodReturnType = subst.substitute(returnType);
455       }
456       else if (resolve instanceof PsiClass) {
457         if (resolve == JavaPsiFacade.getElementFactory(resolve.getProject()).getArrayClass(PsiUtil.getLanguageLevel(resolve))) {
458           final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters();
459           if (typeParameters.length == 1) {
460             final PsiType arrayComponentType = subst.substitute(typeParameters[0]);
461             if (arrayComponentType == null) {
462               return false;
463             }
464             methodReturnType = arrayComponentType.createArrayType();
465           }
466         }
467         containingClass = (PsiClass)resolve;
468       }
469
470       if (methodReturnType == null) {
471         if (containingClass == null) {
472           return false;
473         }
474         methodReturnType = JavaPsiFacade.getElementFactory(getProject()).createType(containingClass, subst);
475       }
476
477       return TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false);
478     }
479     return false;
480   }
481
482   @Nullable
483   @Override
484   public Icon getIcon(int flags) {
485     return AllIcons.Nodes.AnonymousClass;
486   }
487 }