89dc524d85e9b72dfbcaad520c6cc02af77f5160
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / resolve / processors / MethodResolverProcessor.java
1 /*
2  * Copyright 2000-2009 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
17 package org.jetbrains.plugins.groovy.lang.resolve.processors;
18
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.pom.java.LanguageLevel;
21 import com.intellij.psi.*;
22 import com.intellij.psi.scope.JavaScopeProcessorEvent;
23 import com.intellij.psi.search.GlobalSearchScope;
24 import com.intellij.psi.util.PsiTreeUtil;
25 import com.intellij.psi.util.TypeConversionUtil;
26 import com.intellij.util.containers.hash.HashSet;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
30 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
31 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
32 import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
37 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter;
38 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureSignature;
39 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
40 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
41 import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil;
42 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
43 import org.jetbrains.plugins.groovy.lang.resolve.DominanceAwareMethod;
44 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
45
46 import java.util.*;
47
48 /**
49  * @author ven
50  */
51 public class MethodResolverProcessor extends ResolverProcessor {
52   private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.lang.resolve.processors.MethodResolverProcessor");
53   private final PsiType myThisType;
54   @Nullable
55   private final PsiType[] myArgumentTypes;
56   private final PsiType[] myTypeArguments;
57   private final boolean myAllVariants;
58
59   private final Set<GroovyResolveResult> myInapplicableCandidates = new LinkedHashSet<GroovyResolveResult>();
60   private final boolean myIsConstructor;
61
62   private boolean myStopExecuting = false;
63
64   public MethodResolverProcessor(String name, GroovyPsiElement place, boolean isConstructor, PsiType thisType, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments) {
65     this(name, place, isConstructor, thisType, argumentTypes, typeArguments, false);
66   }
67   public MethodResolverProcessor(String name, GroovyPsiElement place, boolean isConstructor, PsiType thisType, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments, boolean allVariants) {
68     super(name, RESOLVE_KINDS_METHOD_PROPERTY, place, PsiType.EMPTY_ARRAY);
69     myIsConstructor = isConstructor;
70     myThisType = thisType;
71     myArgumentTypes = argumentTypes;
72     myTypeArguments = typeArguments;
73     myAllVariants = allVariants;
74   }
75
76   public boolean execute(PsiElement element, ResolveState state) {
77     if (myStopExecuting) {
78       return false;
79     }
80     PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
81     if (element instanceof PsiMethod) {
82       PsiMethod method = (PsiMethod) element;
83
84       if (method.isConstructor() != myIsConstructor) return true;
85       if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
86       substitutor = obtainSubstitutor(substitutor, method);
87       boolean isAccessible = isAccessible(method);
88       GroovyPsiElement fileResolveContext = state.get(RESOLVE_CONTEXT);
89       boolean isStaticsOK = isStaticsOK(method, fileResolveContext);
90       if (!myAllVariants && PsiUtil.isApplicable(myArgumentTypes, method, substitutor, fileResolveContext instanceof GrMethodCallExpression, (GroovyPsiElement)myPlace) && isStaticsOK) {
91         addCandidate(new GroovyResolveResultImpl(method, fileResolveContext, substitutor, isAccessible, isStaticsOK));
92       } else {
93         myInapplicableCandidates.add(new GroovyResolveResultImpl(method, fileResolveContext, substitutor, isAccessible, isStaticsOK));
94       }
95
96       return true;
97     }
98
99     return true;
100   }
101
102   private PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method) {
103     final PsiTypeParameter[] typeParameters = method.getTypeParameters();
104     if (myTypeArguments.length == typeParameters.length) {
105       for (int i = 0; i < typeParameters.length; i++) {
106         PsiTypeParameter typeParameter = typeParameters[i];
107         final PsiType typeArgument = myTypeArguments[i];
108         substitutor = substitutor.put(typeParameter, typeArgument);
109       }
110       return substitutor;
111     }
112
113     if (argumentsSupplied() && method.hasTypeParameters()) {
114       PsiType[] argTypes = myArgumentTypes;
115       if (method instanceof GrGdkMethod) {
116         assert argTypes != null;
117         //type inference should be performed from static method
118         PsiType[] newArgTypes = new PsiType[argTypes.length + 1];
119         newArgTypes[0] = myThisType;
120         System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length);
121         argTypes = newArgTypes;
122
123         method = ((GrGdkMethod) method).getStaticMethod();
124         LOG.assertTrue(method.isValid());
125       }
126       return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes);
127     }
128
129     return substitutor;
130   }
131
132   private PsiSubstitutor inferMethodTypeParameters(PsiMethod method, PsiSubstitutor partialSubstitutor, final PsiTypeParameter[] typeParameters, final PsiType[] argTypes) {
133     if (typeParameters.length == 0) return partialSubstitutor;
134
135     if (argumentsSupplied()) {
136       final GrClosureSignature erasedSignature = GrClosureSignatureUtil.createSignatureWithErasedParameterTypes(method);
137
138       final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor);
139       final GrClosureParameter[] params = signature.getParameters();
140
141       final GrClosureSignatureUtil.ArgInfo<PsiType>[] argInfos =
142         GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, (GroovyPsiElement)myPlace, myAllVariants);
143       if (argInfos ==  null) return partialSubstitutor;
144
145       int max = Math.max(params.length, argTypes.length);
146
147       PsiType[] parameterTypes = new PsiType[max];
148       PsiType[] argumentTypes = new PsiType[max];
149       int i = 0;
150       for (int paramIndex = 0; paramIndex < argInfos.length; paramIndex++) {
151         PsiType paramType = params[paramIndex].getType();
152
153         GrClosureSignatureUtil.ArgInfo<PsiType> argInfo = argInfos[paramIndex];
154         if (argInfo != null) {
155           if (argInfo.isMultiArg) {
156             if (paramType instanceof PsiArrayType) paramType = ((PsiArrayType)paramType).getComponentType();
157           }
158           for (PsiType type : argInfo.args) {
159             argumentTypes[i] = handleConversion(paramType, type);
160             parameterTypes[i] = paramType;
161             i++;
162           }
163         } else {
164           parameterTypes[i] = paramType;
165           argumentTypes[i] = PsiType.NULL;
166           i++;
167         }
168       }
169       final PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper();
170       PsiSubstitutor substitutor = helper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.HIGHEST);
171       for (PsiTypeParameter typeParameter : typeParameters) {
172         if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
173           substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor, helper);
174         }
175       }
176
177       return partialSubstitutor.putAll(substitutor);
178     }
179
180     return partialSubstitutor;
181   }
182
183   private PsiType handleConversion(PsiType paramType, PsiType argType) {
184     final GroovyPsiElement context = (GroovyPsiElement)myPlace;
185     if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, context.getManager(), context.getResolveScope(), false) &&
186         TypesUtil.isAssignableByMethodCallConversion(paramType, argType, context)) {
187       return paramType;
188     }
189     return argType;
190   }
191
192   private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, PsiType lType, PsiSubstitutor substitutor, PsiResolveHelper helper) {
193     if (myPlace != null) {
194       final PsiType inferred = helper.getSubstitutionForTypeParameter(typeParameter, lType, getContextType(), false, LanguageLevel.HIGHEST);
195       if (inferred != PsiType.NULL) {
196         return substitutor.put(typeParameter, inferred);
197       }
198     }
199     return substitutor;
200   }
201
202   @Nullable
203   private PsiType getContextType() {
204     final PsiElement parent = myPlace.getParent().getParent();
205     PsiType rType = null;
206     if (parent instanceof GrReturnStatement) {
207       final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class);
208       if (method != null) rType = method.getReturnType();
209     }
210     else if (parent instanceof GrAssignmentExpression && myPlace.equals(((GrAssignmentExpression)parent).getRValue())) {
211       rType = ((GrAssignmentExpression)parent).getLValue().getType();
212     }
213     else if (parent instanceof GrVariable) {
214       rType = ((GrVariable)parent).getDeclaredType();
215     }
216     return rType;
217   }
218
219   @NotNull
220   public GroovyResolveResult[] getCandidates() {
221     if (!myAllVariants && super.hasCandidates()) {
222       return filterCandidates();
223     }
224     if (!myInapplicableCandidates.isEmpty()) {
225       final Set<GroovyResolveResult> resultSet =
226         myAllVariants ? myInapplicableCandidates : filterCorrectParameterCount(myInapplicableCandidates);
227       return ResolveUtil.filterSameSignatureCandidates(resultSet, myArgumentTypes != null ? myArgumentTypes.length : -1);
228     }
229     return GroovyResolveResult.EMPTY_ARRAY;
230   }
231
232   private Set<GroovyResolveResult> filterCorrectParameterCount(Set<GroovyResolveResult> candidates) {
233     if (myArgumentTypes == null) return candidates;
234     Set<GroovyResolveResult> result = new HashSet<GroovyResolveResult>();
235     for (GroovyResolveResult candidate : candidates) {
236       final PsiElement element = candidate.getElement();
237       if (element instanceof PsiMethod && ((PsiMethod)element).getParameterList().getParametersCount() == myArgumentTypes.length) {
238         result.add(candidate);
239       }
240     }
241     if (result.size() > 0) return result;
242     return candidates;
243   }
244
245   private GroovyResolveResult[] filterCandidates() {
246     Set<GroovyResolveResult> array = getCandidatesInternal();
247     if (array.size() == 1) return array.toArray(new GroovyResolveResult[array.size()]);
248
249     List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
250
251     Iterator<GroovyResolveResult> itr = array.iterator();
252
253     result.add(itr.next());
254
255     GlobalSearchScope scope = myPlace.getResolveScope();
256
257     Outer:
258     while (itr.hasNext()) {
259       GroovyResolveResult resolveResult = itr.next();
260       PsiElement currentElement = resolveResult.getElement();
261       if (currentElement instanceof PsiMethod) {
262         PsiMethod currentMethod = (PsiMethod) currentElement;
263         for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
264           final GroovyResolveResult otherResolveResult = iterator.next();
265           PsiElement element = otherResolveResult.getElement();
266           if (element instanceof PsiMethod) {
267             PsiMethod method = (PsiMethod) element;
268             final int res = compareMethods(currentMethod, resolveResult.getSubstitutor(), method, otherResolveResult.getSubstitutor(), scope);
269             if (res > 0) {
270               continue Outer;
271             }
272             else if (res < 0) {
273               iterator.remove();
274             }
275           }
276         }
277       }
278
279       result.add(resolveResult);
280     }
281
282     return result.toArray(new GroovyResolveResult[result.size()]);
283   }
284
285   private int compareMethods(PsiMethod method1,
286                              PsiSubstitutor substitutor1,
287                              PsiMethod method2,
288                              PsiSubstitutor substitutor2,
289                              GlobalSearchScope scope) {
290     if (!method1.getName().equals(method2.getName())) return 0;
291
292     if (method2 instanceof DominanceAwareMethod && ((DominanceAwareMethod)method2).isMoreConcreteThan(substitutor2, method1, substitutor1, (GroovyPsiElement)myPlace)) {
293       return 1;
294     }
295
296     if (method1 instanceof DominanceAwareMethod && ((DominanceAwareMethod)method1).isMoreConcreteThan(substitutor1, method2, substitutor2, (GroovyPsiElement)myPlace)) {
297       return -1;
298     }
299
300     if (dominated(method1, substitutor1, method2, substitutor2, scope)) {
301       return 1;
302     }
303     if (dominated(method2, substitutor2, method1, substitutor1, scope)) {
304       return -1;
305     }
306
307     return 0;
308   }
309
310   private boolean dominated(PsiMethod method1,
311                             PsiSubstitutor substitutor1,
312                             PsiMethod method2,
313                             PsiSubstitutor substitutor2,
314                             GlobalSearchScope scope) {  //method1 has more general parameter types thn method2
315     if (!method1.getName().equals(method2.getName())) return false;
316
317     PsiType[] argTypes = myArgumentTypes;
318     if (method1 instanceof GrGdkMethod && method2 instanceof GrGdkMethod) {
319       method1 = ((GrGdkMethod)method1).getStaticMethod();
320       method2 = ((GrGdkMethod)method2).getStaticMethod();
321       if (myArgumentTypes != null) {
322         argTypes = new PsiType[argTypes.length + 1];
323         System.arraycopy(myArgumentTypes, 0, argTypes, 1, myArgumentTypes.length);
324         argTypes[0] = myThisType;
325       }
326     }
327
328     if (myIsConstructor && argTypes != null && argTypes.length == 1) {
329       if (method1.getParameterList().getParametersCount() == 0) return true;
330       if (method2.getParameterList().getParametersCount() == 0) return false;
331     }
332
333     PsiParameter[] params1 = method1.getParameterList().getParameters();
334     PsiParameter[] params2 = method2.getParameterList().getParameters();
335     if (argTypes == null && params1.length != params2.length) return false;
336
337     if (params1.length < params2.length) {
338       if (params1.length == 0) return false;
339       final PsiType lastType = params1[params1.length - 1].getType(); //varargs applicability
340       return lastType instanceof PsiArrayType;
341     }
342
343     PsiManager manager = method1.getManager();
344     for (int i = 0; i < params2.length; i++) {
345       PsiType type1 = substitutor1.substitute(params1[i].getType());
346       PsiType type2 = substitutor2.substitute(params2[i].getType());
347
348       if (argTypes != null && argTypes.length > i) {
349         PsiType argType = argTypes[i];
350         if (argType != null) {
351           final boolean converts1 = TypesUtil.isAssignable(type1, argType, manager, scope, false);
352           final boolean converts2 = TypesUtil.isAssignable(type2, argType, manager, scope, false);
353           if (converts1 != converts2) {
354             return converts2;
355           }
356         }
357       }
358
359       if (!typesAgree(manager, scope, type1, type2)) return false;
360     }
361
362     return true;
363   }
364
365   private boolean typesAgree(PsiManager manager, GlobalSearchScope scope, PsiType type1, PsiType type2) {
366     if (argumentsSupplied() && type1 instanceof PsiArrayType && !(type2 instanceof PsiArrayType)) {
367       type1 = ((PsiArrayType) type1).getComponentType();
368     }
369     return argumentsSupplied() ? //resolve, otherwise same_name_variants
370         TypesUtil.isAssignable(type1, type2, manager, scope, false) :
371         type1.equals(type2);
372   }
373
374   private boolean argumentsSupplied() {
375     return myArgumentTypes != null;
376   }
377
378
379   public boolean hasCandidates() {
380     return super.hasCandidates() || !myInapplicableCandidates.isEmpty();
381   }
382
383   public boolean hasApplicableCandidates() {
384     return super.hasCandidates();
385   }
386
387   @Nullable
388   public PsiType[] getArgumentTypes() {
389     return myArgumentTypes;
390   }
391
392   @Override
393   public void handleEvent(Event event, Object associated) {
394     super.handleEvent(event, associated);
395     if (JavaScopeProcessorEvent.CHANGE_LEVEL == event && super.hasCandidates()) {
396       myStopExecuting = true;
397     }
398   }
399 }