[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / tree / java / MethodReferenceResolver.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.impl.source.tree.java;
3
4 import com.intellij.openapi.util.text.StringUtil;
5 import com.intellij.psi.*;
6 import com.intellij.psi.impl.source.resolve.ParameterTypeInferencePolicy;
7 import com.intellij.psi.impl.source.resolve.ResolveCache;
8 import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
9 import com.intellij.psi.impl.source.resolve.graphInference.constraints.PsiMethodReferenceCompatibilityConstraint;
10 import com.intellij.psi.infos.CandidateInfo;
11 import com.intellij.psi.infos.ClassCandidateInfo;
12 import com.intellij.psi.infos.MethodCandidateInfo;
13 import com.intellij.psi.scope.JavaScopeProcessorEvent;
14 import com.intellij.psi.scope.PsiConflictResolver;
15 import com.intellij.psi.scope.conflictResolvers.JavaMethodsConflictResolver;
16 import com.intellij.psi.scope.processor.MethodCandidatesProcessor;
17 import com.intellij.psi.util.MethodSignature;
18 import com.intellij.psi.util.PsiTreeUtil;
19 import com.intellij.psi.util.PsiUtil;
20 import com.intellij.psi.util.TypeConversionUtil;
21 import com.intellij.util.SmartList;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24
25 import java.util.ArrayList;
26 import java.util.List;
27
28 public class MethodReferenceResolver implements ResolveCache.PolyVariantContextResolver<PsiMethodReferenceExpressionImpl> {
29   @NotNull
30   @Override
31   public JavaResolveResult[] resolve(@NotNull final PsiMethodReferenceExpressionImpl reference, @NotNull final PsiFile containingFile, boolean incompleteCode) {
32     final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(reference);
33
34     final PsiClass containingClass = qualifierResolveResult.getContainingClass();
35     PsiSubstitutor substitutor = qualifierResolveResult.getSubstitutor();
36
37     if (containingClass != null) {
38       final PsiElement element = reference.getReferenceNameElement();
39       final boolean isConstructor = reference.isConstructor();
40       if (element instanceof PsiIdentifier || isConstructor) {
41         if (isConstructor && !canBeConstructed(containingClass)) {
42           return JavaResolveResult.EMPTY_ARRAY;
43         }
44         final PsiType functionalInterfaceType = reference.getFunctionalInterfaceType();
45         final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
46         final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
47         final PsiSubstitutor functionalInterfaceSubstitutor = interfaceMethod != null ? LambdaUtil.getSubstitutor(interfaceMethod, resolveResult) : null;
48         final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(functionalInterfaceSubstitutor) : null;
49         final PsiType interfaceMethodReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
50         if (isConstructor && containingClass.getConstructors().length == 0) {
51           if (interfaceMethod != null) {
52             final PsiClassType returnType = composeReturnType(containingClass, substitutor);
53             final InferenceSession session = new InferenceSession(containingClass.getTypeParameters(), substitutor, reference.getManager(), null);
54             if (!(session.isProperType(session.substituteWithInferenceVariables(returnType)) && session.isProperType(interfaceMethodReturnType))) {
55               session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType, reference);
56               substitutor = session.infer();
57             }
58           }
59           ClassCandidateInfo candidateInfo = null;
60           final boolean isArray = PsiUtil.isArrayClass(containingClass);
61           if (signature == null ||
62               !isArray && (containingClass.getContainingClass() == null || !isLocatedInStaticContext(containingClass, reference)) && signature.getParameterTypes().length == 0 ||
63               isArray && arrayCreationSignature(signature)) {
64             candidateInfo = new ClassCandidateInfo(containingClass, substitutor);
65           }
66           return candidateInfo == null ? JavaResolveResult.EMPTY_ARRAY : new JavaResolveResult[]{candidateInfo};
67         }
68
69         final PsiConflictResolver conflictResolver = createResolver(reference, qualifierResolveResult, interfaceMethod, signature);
70         final MethodCandidatesProcessor processor =
71           new MethodCandidatesProcessor(reference, containingFile, new PsiConflictResolver[] {conflictResolver}, new SmartList<>()) {
72             @Override
73             protected boolean acceptVarargs() {
74               return true;
75             }
76
77             @Override
78             protected MethodCandidateInfo createCandidateInfo(@NotNull final PsiMethod method,
79                                                               @NotNull final PsiSubstitutor substitutor,
80                                                               final boolean staticProblem,
81                                                               final boolean accessible,
82                                                               final boolean varargs) {
83               final PsiExpressionList argumentList = getArgumentList();
84               final PsiType[] typeParameters = reference.getTypeParameters();
85               return new MethodCandidateInfo(method, substitutor, !accessible, staticProblem, argumentList, myCurrentFileContext,
86                                              argumentList != null ? argumentList.getExpressionTypes() : null,
87                                              method.hasTypeParameters() && typeParameters.length > 0 ? typeParameters : null,
88                                              getLanguageLevel()) {
89                 @Override
90                 public boolean isVarargs() {
91                   return varargs;
92                 }
93
94                 @NotNull
95                 @Override
96                 public PsiSubstitutor inferTypeArguments(@NotNull ParameterTypeInferencePolicy policy, boolean includeReturnConstraint) {
97                   return inferTypeArguments(includeReturnConstraint);
98                 }
99
100                 private PsiSubstitutor inferTypeArguments(boolean includeReturnConstraint) {
101                   if (interfaceMethod == null) return substitutor;
102                   final InferenceSession session = new InferenceSession(method.getTypeParameters(), substitutor, reference.getManager(), reference);
103                   session.initThrowsConstraints(method);
104                   final PsiSubstitutor psiSubstitutor = session.collectApplicabilityConstraints(reference, this, functionalInterfaceType);
105                   if (psiSubstitutor != null) {
106                     return psiSubstitutor;
107                   }
108
109                   if (!session.repeatInferencePhases()) {
110                     List<String> errorMessages = session.getIncompatibleErrorMessages();
111                     if (errorMessages != null) {
112                       setApplicabilityError(StringUtil.join(errorMessages, "\n"));
113                     }
114                     return substitutor;
115                   }
116
117                   if (includeReturnConstraint && !PsiType.VOID.equals(interfaceMethodReturnType) && interfaceMethodReturnType != null) {
118                     PsiSubstitutor subst = PsiMethodReferenceCompatibilityConstraint.getSubstitutor(signature, qualifierResolveResult, method, containingClass, reference);
119                     final PsiType returnType = method.isConstructor() ? composeReturnType(containingClass, subst) : subst.substitute(method.getReturnType());
120                     if (returnType != null) {
121                       session.registerReturnTypeConstraints(returnType, interfaceMethodReturnType, reference);
122                     }
123                   }
124                   return session.infer(method.getParameterList().getParameters(), null, null, null);
125                 }
126
127                 @Override
128                 public boolean isApplicable() {
129                   if (signature == null) return false;
130                   if (getInferenceErrorMessageAssumeAlreadyComputed() != null) return false;
131                   final PsiType[] argTypes = signature.getParameterTypes();
132                   boolean hasReceiver = PsiMethodReferenceUtil.isSecondSearchPossible(argTypes, qualifierResolveResult, reference);
133
134                   return MethodReferenceConflictResolver.isApplicableByFirstSearch(this, argTypes, hasReceiver, reference, interfaceMethod.isVarArgs(), interfaceMethod) != null;
135                 }
136               };
137             }
138         };
139         processor.setIsConstructor(isConstructor);
140         processor.setName(isConstructor ? containingClass.getName() : element.getText());
141         final PsiExpression expression = reference.getQualifierExpression();
142         if (expression == null || !(expression.getType() instanceof PsiArrayType)) {
143           processor.setAccessClass(containingClass);
144         }
145
146         if (qualifierResolveResult.isReferenceTypeQualified() && isLocatedInStaticContext(containingClass, reference)) {
147            processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null);
148         }
149         ResolveState state = ResolveState.initial().put(PsiSubstitutor.KEY, substitutor);
150         containingClass.processDeclarations(processor, state, reference, reference);
151         return processor.getResult();
152       }
153     }
154     return JavaResolveResult.EMPTY_ARRAY;
155   }
156
157   public static boolean canBeConstructed(@NotNull PsiClass psiClass) {
158     return !psiClass.isEnum() && !psiClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(psiClass instanceof PsiTypeParameter);
159   }
160
161   private static boolean isLocatedInStaticContext(PsiClass containingClass, PsiMethodReferenceExpression reference) {
162     final PsiClass gContainingClass = containingClass.getContainingClass();
163     if (gContainingClass == null || !containingClass.hasModifierProperty(PsiModifier.STATIC)) {
164       PsiClass aClass = null;
165       if (PsiTreeUtil.isAncestor(gContainingClass != null ? gContainingClass : containingClass, reference, false)) {
166         aClass = gContainingClass != null ? gContainingClass : containingClass;
167       }
168       if (PsiUtil.getEnclosingStaticElement(reference, aClass) != null) {
169         return true;
170       }
171     }
172     return false;
173   }
174
175   protected PsiConflictResolver createResolver(PsiMethodReferenceExpressionImpl referenceExpression,
176                                                PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult,
177                                                PsiMethod interfaceMethod,
178                                                MethodSignature signature) {
179     return new MethodReferenceConflictResolver(referenceExpression, qualifierResolveResult, signature, interfaceMethod);
180   }
181
182   private static PsiClassType composeReturnType(PsiClass containingClass, PsiSubstitutor substitutor) {
183     final boolean isRawSubst = PsiUtil.isRawSubstitutor(containingClass, substitutor);
184     return JavaPsiFacade.getElementFactory(containingClass.getProject()).createType(containingClass, isRawSubst ? PsiSubstitutor.EMPTY : substitutor);
185   }
186
187   private static class MethodReferenceConflictResolver extends JavaMethodsConflictResolver {
188     private final MethodSignature mySignature;
189     private final PsiMethod myInterfaceMethod;
190     private final PsiMethodReferenceExpressionImpl myReferenceExpression;
191     private final PsiMethodReferenceUtil.QualifierResolveResult myQualifierResolveResult;
192     private final boolean myFunctionalMethodVarArgs;
193
194     private MethodReferenceConflictResolver(PsiMethodReferenceExpressionImpl referenceExpression,
195                                             PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult,
196                                             @Nullable MethodSignature signature, PsiMethod interfaceMethod) {
197       super(referenceExpression, signature != null ? signature.getParameterTypes() : PsiType.EMPTY_ARRAY, PsiUtil.getLanguageLevel(referenceExpression));
198       myReferenceExpression = referenceExpression;
199       myQualifierResolveResult = qualifierResolveResult;
200       myFunctionalMethodVarArgs =  interfaceMethod != null && interfaceMethod.isVarArgs();
201       mySignature = signature;
202       myInterfaceMethod = interfaceMethod;
203     }
204
205     @Override
206     protected int getPertinentApplicabilityLevel(@NotNull MethodCandidateInfo conflict) {
207       return conflict.isVarargs() ? MethodCandidateInfo.ApplicabilityLevel.VARARGS : MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY;
208     }
209
210     @Nullable
211     @Override
212     protected CandidateInfo guardedOverloadResolution(@NotNull List<CandidateInfo> conflicts) {
213       if (mySignature == null) return null;
214
215       if (conflicts.isEmpty()) return null;
216       if (conflicts.size() == 1) return conflicts.get(0);
217
218       checkSameSignatures(conflicts);
219       if (conflicts.size() == 1) return  conflicts.get(0);
220
221       checkAccessStaticLevels(conflicts, true);
222       if (conflicts.size() == 1) return  conflicts.get(0);
223
224       final PsiType[] argTypes = mySignature.getParameterTypes();
225       boolean hasReceiver = PsiMethodReferenceUtil.isSecondSearchPossible(argTypes, myQualifierResolveResult, myReferenceExpression);
226
227       final List<CandidateInfo> firstCandidates = new ArrayList<>();
228       final List<CandidateInfo> secondCandidates = new ArrayList<>();
229
230       for (CandidateInfo conflict : conflicts) {
231         if (!(conflict instanceof MethodCandidateInfo)) continue;
232         final Boolean applicableByFirstSearch = isApplicableByFirstSearch(conflict, argTypes, hasReceiver, myReferenceExpression, myFunctionalMethodVarArgs, myInterfaceMethod);
233         if (applicableByFirstSearch != null) {
234           (applicableByFirstSearch ? firstCandidates : secondCandidates).add(conflict);
235         }
236       }
237
238       if (myQualifierResolveResult.isReferenceTypeQualified() && myReferenceExpression.getReferenceNameElement() instanceof PsiIdentifier) {
239         //If the first search produces a static method, and no non-static method is applicable for the second search, then the result of the first search is the compile-time declaration.
240         CandidateInfo candidateInfo = filterStaticCorrectCandidates(firstCandidates, secondCandidates, true);
241         if (candidateInfo != null) {
242           return candidateInfo;
243         }
244
245         //If the second search produces a non-static method, and no static method is applicable for the first search, then the result of the second search is the compile-time declaration.
246         candidateInfo = filterStaticCorrectCandidates(secondCandidates, firstCandidates, false);
247         if (candidateInfo != null) {
248           return candidateInfo;
249         }
250       }
251
252       CandidateInfo candidateInfo = resolveConflicts(firstCandidates, secondCandidates, MethodCandidateInfo.ApplicabilityLevel.FIXED_ARITY);
253       if (candidateInfo != null) {
254         return candidateInfo;
255       }
256
257       candidateInfo = resolveConflicts(firstCandidates, secondCandidates, MethodCandidateInfo.ApplicabilityLevel.VARARGS);
258       if (candidateInfo != null) {
259         return candidateInfo;
260       }
261
262       if (firstCandidates.isEmpty() && secondCandidates.isEmpty()) {
263         return null;
264       }
265
266       conflicts.clear();
267       firstCandidates.addAll(secondCandidates);
268       conflicts.addAll(firstCandidates);
269       return null;
270     }
271
272     private static Boolean isApplicableByFirstSearch(CandidateInfo conflict,
273                                                      PsiType[] functionalInterfaceParamTypes,
274                                                      boolean hasReceiver,
275                                                      PsiMethodReferenceExpression referenceExpression,
276                                                      boolean functionalMethodVarArgs,
277                                                      PsiMethod interfaceMethod) {
278
279       final PsiMethod psiMethod = ((MethodCandidateInfo)conflict).getElement();
280
281       final PsiSubstitutor substitutor = ((MethodCandidateInfo)conflict).getSubstitutor(false);
282       if (((MethodCandidateInfo)conflict).getInferenceErrorMessage() != null) return null;
283       final PsiType[] parameterTypes = psiMethod.getSignature(substitutor).getParameterTypes();
284
285       final boolean varargs = ((MethodCandidateInfo)conflict).isVarargs();
286       if (varargs && (!psiMethod.isVarArgs() || functionalMethodVarArgs)) {
287         return null;
288       }
289
290       if ((varargs || functionalInterfaceParamTypes.length == parameterTypes.length) &&
291           isCorrectAssignment(parameterTypes, functionalInterfaceParamTypes, interfaceMethod, varargs, referenceExpression, conflict, 0)) {
292         //reject static interface methods called on something else but interface class
293         if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
294           PsiClass containingClass = psiMethod.getContainingClass();
295           if (containingClass != null && containingClass.isInterface()) {
296             final PsiClass qualifierClass = PsiMethodReferenceUtil.getQualifierResolveResult(referenceExpression).getContainingClass();
297             if (!containingClass.getManager().areElementsEquivalent(qualifierClass, containingClass)) {
298               return null;
299             }
300           }
301         }
302         return true;
303       }
304
305       if (hasReceiver &&
306           (varargs || functionalInterfaceParamTypes.length == parameterTypes.length + 1) &&
307           isCorrectAssignment(parameterTypes, functionalInterfaceParamTypes, interfaceMethod, varargs, referenceExpression, conflict, 1)) {
308         return false;
309       }
310       return null;
311     }
312
313     private static boolean isCorrectAssignment(PsiType[] parameterTypes,
314                                                PsiType[] functionalInterfaceParamTypes,
315                                                PsiMethod interfaceMethod,
316                                                boolean varargs,
317                                                PsiMethodReferenceExpression referenceExpression,
318                                                CandidateInfo conflict,
319                                                int offset) {
320       final int min = Math.min(parameterTypes.length, functionalInterfaceParamTypes.length - offset);
321       for (int i = 0; i < min; i++) {
322         final PsiType argType = PsiUtil.captureToplevelWildcards(functionalInterfaceParamTypes[i + offset], interfaceMethod.getParameterList().getParameters()[i]);
323         final PsiType parameterType = parameterTypes[i];
324         if (varargs && i == parameterTypes.length - 1) {
325           if (!TypeConversionUtil.isAssignable(parameterType, argType) &&
326               !TypeConversionUtil.isAssignable(((PsiArrayType)parameterType).getComponentType(), argType)) {
327             reportParameterConflict(referenceExpression, conflict, argType, parameterType);
328             return false;
329           }
330         }
331         else if (!TypeConversionUtil.isAssignable(parameterType, argType)) {
332           reportParameterConflict(referenceExpression, conflict, argType, parameterType);
333           return false;
334         }
335       }
336       return !varargs || parameterTypes.length - 1 <= functionalInterfaceParamTypes.length - offset;
337     }
338
339     private static void reportParameterConflict(PsiMethodReferenceExpression referenceExpression,
340                                                 CandidateInfo conflict,
341                                                 PsiType argType, 
342                                                 PsiType parameterType) {
343       if (conflict instanceof MethodCandidateInfo) {
344         ((MethodCandidateInfo)conflict).setApplicabilityError("Invalid " +
345                                                               (referenceExpression.isConstructor() ? "constructor" :"method") +
346                                                               " reference: " + argType.getPresentableText() + " cannot be converted to " + parameterType.getPresentableText());
347       }
348     }
349
350     private CandidateInfo resolveConflicts(List<CandidateInfo> firstCandidates, List<CandidateInfo> secondCandidates, int applicabilityLevel) {
351
352       final int firstApplicability = checkApplicability(firstCandidates);
353       checkSpecifics(firstCandidates, applicabilityLevel);
354
355       final int secondApplicability = checkApplicability(secondCandidates);
356       checkSpecifics(secondCandidates, applicabilityLevel, null, 1);
357
358       if (firstApplicability < secondApplicability) {
359         return secondCandidates.size() == 1 ? secondCandidates.get(0) : null;
360       }
361       
362       if (secondApplicability < firstApplicability) {
363         return firstCandidates.size() == 1 ? firstCandidates.get(0) : null;
364       }
365
366       return firstCandidates.size() + secondCandidates.size() == 1
367              ? firstCandidates.isEmpty()
368                ? secondCandidates.get(0)
369                : firstCandidates.get(0)
370              : null;
371     }
372
373     @Override
374     protected boolean nonComparable(@NotNull CandidateInfo method, @NotNull CandidateInfo conflict, boolean fixedArity) {
375       if (method == conflict) return true;
376       PsiElement psiElement = method.getElement();
377       PsiElement conflictElement = conflict.getElement();
378       if (psiElement instanceof PsiMethod && conflictElement instanceof PsiMethod) {
379         if (fixedArity && ((PsiMethod)psiElement).getParameterList().getParametersCount() != ((PsiMethod)conflictElement).getParameterList().getParametersCount()) {
380           return true;
381         }
382       }
383       return false;
384     }
385
386     /**
387      * 15.13.1
388      */
389     private static CandidateInfo filterStaticCorrectCandidates(List<CandidateInfo> firstCandidates,
390                                                                List<CandidateInfo> secondCandidates,
391                                                                boolean shouldBeStatic) {
392       if (firstCandidates.size() == 1) {
393         final CandidateInfo candidateInfo = firstCandidates.get(0);
394         final PsiElement element = candidateInfo.getElement();
395         if (element instanceof PsiMethod) {
396           final boolean isStatic = ((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC);
397           if (shouldBeStatic == isStatic) {
398             for (CandidateInfo secondCandidate : secondCandidates) {
399               final PsiElement psiElement = secondCandidate.getElement();
400               if (psiElement instanceof PsiMethod) {
401                 final boolean oppositeStatic = ((PsiMethod)psiElement).hasModifierProperty(PsiModifier.STATIC);
402                 if (shouldBeStatic != oppositeStatic) {
403                   return null;
404                 }
405               }
406             }
407             return candidateInfo;
408           }
409         }
410       }
411       return null;
412     }
413   }
414
415   private static boolean arrayCreationSignature(MethodSignature signature) {
416     final PsiType[] parameterTypes = signature.getParameterTypes();
417     return parameterTypes.length == 1 && parameterTypes[0] != null && TypeConversionUtil.isAssignable(PsiType.INT, parameterTypes[0]);
418   }
419 }