type inference for closures in groovy1.8
[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.GrExpression;
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, state);
87       boolean isAccessible = isAccessible(method);
88       GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT);
89       boolean isStaticsOK = isStaticsOK(method, resolveContext);
90       if (!myAllVariants &&
91           PsiUtil.isApplicable(myArgumentTypes, method, substitutor, ResolveUtil.isInUseScope(resolveContext), (GroovyPsiElement)myPlace) &&
92           isStaticsOK) {
93         addCandidate(new GroovyResolveResultImpl(method, resolveContext, substitutor, isAccessible, isStaticsOK));
94       } else {
95         myInapplicableCandidates.add(new GroovyResolveResultImpl(method, resolveContext, substitutor, isAccessible, isStaticsOK));
96       }
97
98       return true;
99     }
100
101     return true;
102   }
103
104   private PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method, ResolveState state) {
105     final PsiTypeParameter[] typeParameters = method.getTypeParameters();
106     if (myTypeArguments.length == typeParameters.length) {
107       for (int i = 0; i < typeParameters.length; i++) {
108         PsiTypeParameter typeParameter = typeParameters[i];
109         final PsiType typeArgument = myTypeArguments[i];
110         substitutor = substitutor.put(typeParameter, typeArgument);
111       }
112       return substitutor;
113     }
114
115     if (argumentsSupplied() && method.hasTypeParameters()) {
116       PsiType[] argTypes = myArgumentTypes;
117       if (method instanceof GrGdkMethod) {
118         assert argTypes != null;
119         //type inference should be performed from static method
120         PsiType[] newArgTypes = new PsiType[argTypes.length + 1];
121         final GroovyPsiElement resolveContext = state.get(RESOLVE_CONTEXT);
122         if (ResolveUtil.isInWithContext(resolveContext)) {
123           newArgTypes[0] = ((GrExpression)resolveContext).getType();
124         }
125         else {
126           newArgTypes[0] = myThisType;
127         }
128         System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length);
129         argTypes = newArgTypes;
130
131         method = ((GrGdkMethod) method).getStaticMethod();
132         LOG.assertTrue(method.isValid());
133       }
134       return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes);
135     }
136
137     return substitutor;
138   }
139
140   private PsiSubstitutor inferMethodTypeParameters(PsiMethod method, PsiSubstitutor partialSubstitutor, final PsiTypeParameter[] typeParameters, final PsiType[] argTypes) {
141     if (typeParameters.length == 0) return partialSubstitutor;
142
143     if (argumentsSupplied()) {
144       final GrClosureSignature erasedSignature = GrClosureSignatureUtil.createSignatureWithErasedParameterTypes(method);
145
146       final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor);
147       final GrClosureParameter[] params = signature.getParameters();
148
149       final GrClosureSignatureUtil.ArgInfo<PsiType>[] argInfos =
150         GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, (GroovyPsiElement)myPlace, myAllVariants);
151       if (argInfos ==  null) return partialSubstitutor;
152
153       int max = Math.max(params.length, argTypes.length);
154
155       PsiType[] parameterTypes = new PsiType[max];
156       PsiType[] argumentTypes = new PsiType[max];
157       int i = 0;
158       for (int paramIndex = 0; paramIndex < argInfos.length; paramIndex++) {
159         PsiType paramType = params[paramIndex].getType();
160
161         GrClosureSignatureUtil.ArgInfo<PsiType> argInfo = argInfos[paramIndex];
162         if (argInfo != null) {
163           if (argInfo.isMultiArg) {
164             if (paramType instanceof PsiArrayType) paramType = ((PsiArrayType)paramType).getComponentType();
165           }
166           for (PsiType type : argInfo.args) {
167             argumentTypes[i] = handleConversion(paramType, type);
168             parameterTypes[i] = paramType;
169             i++;
170           }
171         } else {
172           parameterTypes[i] = paramType;
173           argumentTypes[i] = PsiType.NULL;
174           i++;
175         }
176       }
177       final PsiResolveHelper helper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper();
178       PsiSubstitutor substitutor = helper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.HIGHEST);
179       for (PsiTypeParameter typeParameter : typeParameters) {
180         if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
181           substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor, helper);
182         }
183       }
184
185       return partialSubstitutor.putAll(substitutor);
186     }
187
188     return partialSubstitutor;
189   }
190
191   private PsiType handleConversion(PsiType paramType, PsiType argType) {
192     final GroovyPsiElement context = (GroovyPsiElement)myPlace;
193     if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, context.getManager(), context.getResolveScope(), false) &&
194         TypesUtil.isAssignableByMethodCallConversion(paramType, argType, context)) {
195       return paramType;
196     }
197     return argType;
198   }
199
200   private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, PsiType lType, PsiSubstitutor substitutor, PsiResolveHelper helper) {
201     if (myPlace != null) {
202       final PsiType inferred = helper.getSubstitutionForTypeParameter(typeParameter, lType, getContextType(), false, LanguageLevel.HIGHEST);
203       if (inferred != PsiType.NULL) {
204         return substitutor.put(typeParameter, inferred);
205       }
206     }
207     return substitutor;
208   }
209
210   @Nullable
211   private PsiType getContextType() {
212     final PsiElement parent = myPlace.getParent().getParent();
213     PsiType rType = null;
214     if (parent instanceof GrReturnStatement) {
215       final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class);
216       if (method != null) rType = method.getReturnType();
217     }
218     else if (parent instanceof GrAssignmentExpression && myPlace.equals(((GrAssignmentExpression)parent).getRValue())) {
219       rType = ((GrAssignmentExpression)parent).getLValue().getType();
220     }
221     else if (parent instanceof GrVariable) {
222       rType = ((GrVariable)parent).getDeclaredType();
223     }
224     return rType;
225   }
226
227   @NotNull
228   public GroovyResolveResult[] getCandidates() {
229     if (!myAllVariants && super.hasCandidates()) {
230       return filterCandidates();
231     }
232     if (!myInapplicableCandidates.isEmpty()) {
233       final Set<GroovyResolveResult> resultSet =
234         myAllVariants ? myInapplicableCandidates : filterCorrectParameterCount(myInapplicableCandidates);
235       return ResolveUtil.filterSameSignatureCandidates(resultSet, myArgumentTypes != null ? myArgumentTypes.length : -1);
236     }
237     return GroovyResolveResult.EMPTY_ARRAY;
238   }
239
240   private Set<GroovyResolveResult> filterCorrectParameterCount(Set<GroovyResolveResult> candidates) {
241     if (myArgumentTypes == null) return candidates;
242     Set<GroovyResolveResult> result = new HashSet<GroovyResolveResult>();
243     for (GroovyResolveResult candidate : candidates) {
244       final PsiElement element = candidate.getElement();
245       if (element instanceof PsiMethod && ((PsiMethod)element).getParameterList().getParametersCount() == myArgumentTypes.length) {
246         result.add(candidate);
247       }
248     }
249     if (result.size() > 0) return result;
250     return candidates;
251   }
252
253   private GroovyResolveResult[] filterCandidates() {
254     Set<GroovyResolveResult> array = getCandidatesInternal();
255     if (array.size() == 1) return array.toArray(new GroovyResolveResult[array.size()]);
256
257     List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
258
259     Iterator<GroovyResolveResult> itr = array.iterator();
260
261     result.add(itr.next());
262
263     GlobalSearchScope scope = myPlace.getResolveScope();
264
265     Outer:
266     while (itr.hasNext()) {
267       GroovyResolveResult resolveResult = itr.next();
268       PsiElement currentElement = resolveResult.getElement();
269       if (currentElement instanceof PsiMethod) {
270         PsiMethod currentMethod = (PsiMethod) currentElement;
271         for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
272           final GroovyResolveResult otherResolveResult = iterator.next();
273           PsiElement element = otherResolveResult.getElement();
274           if (element instanceof PsiMethod) {
275             PsiMethod method = (PsiMethod) element;
276             final int res = compareMethods(currentMethod, resolveResult.getSubstitutor(), method, otherResolveResult.getSubstitutor(), scope);
277             if (res > 0) {
278               continue Outer;
279             }
280             else if (res < 0) {
281               iterator.remove();
282             }
283           }
284         }
285       }
286
287       result.add(resolveResult);
288     }
289
290     return result.toArray(new GroovyResolveResult[result.size()]);
291   }
292
293   private int compareMethods(PsiMethod method1,
294                              PsiSubstitutor substitutor1,
295                              PsiMethod method2,
296                              PsiSubstitutor substitutor2,
297                              GlobalSearchScope scope) {
298     if (!method1.getName().equals(method2.getName())) return 0;
299
300     if (method2 instanceof DominanceAwareMethod && ((DominanceAwareMethod)method2).isMoreConcreteThan(substitutor2, method1, substitutor1, (GroovyPsiElement)myPlace)) {
301       return 1;
302     }
303
304     if (method1 instanceof DominanceAwareMethod && ((DominanceAwareMethod)method1).isMoreConcreteThan(substitutor1, method2, substitutor2, (GroovyPsiElement)myPlace)) {
305       return -1;
306     }
307
308     if (dominated(method1, substitutor1, method2, substitutor2, scope)) {
309       return 1;
310     }
311     if (dominated(method2, substitutor2, method1, substitutor1, scope)) {
312       return -1;
313     }
314
315     return 0;
316   }
317
318   private boolean dominated(PsiMethod method1,
319                             PsiSubstitutor substitutor1,
320                             PsiMethod method2,
321                             PsiSubstitutor substitutor2,
322                             GlobalSearchScope scope) {  //method1 has more general parameter types thn method2
323     if (!method1.getName().equals(method2.getName())) return false;
324
325     PsiType[] argTypes = myArgumentTypes;
326     if (method1 instanceof GrGdkMethod && method2 instanceof GrGdkMethod) {
327       method1 = ((GrGdkMethod)method1).getStaticMethod();
328       method2 = ((GrGdkMethod)method2).getStaticMethod();
329       if (myArgumentTypes != null) {
330         argTypes = new PsiType[argTypes.length + 1];
331         System.arraycopy(myArgumentTypes, 0, argTypes, 1, myArgumentTypes.length);
332         argTypes[0] = myThisType;
333       }
334     }
335
336     if (myIsConstructor && argTypes != null && argTypes.length == 1) {
337       if (method1.getParameterList().getParametersCount() == 0) return true;
338       if (method2.getParameterList().getParametersCount() == 0) return false;
339     }
340
341     PsiParameter[] params1 = method1.getParameterList().getParameters();
342     PsiParameter[] params2 = method2.getParameterList().getParameters();
343     if (argTypes == null && params1.length != params2.length) return false;
344
345     if (params1.length < params2.length) {
346       if (params1.length == 0) return false;
347       final PsiType lastType = params1[params1.length - 1].getType(); //varargs applicability
348       return lastType instanceof PsiArrayType;
349     }
350
351     PsiManager manager = method1.getManager();
352     for (int i = 0; i < params2.length; i++) {
353       PsiType type1 = substitutor1.substitute(params1[i].getType());
354       PsiType type2 = substitutor2.substitute(params2[i].getType());
355
356       if (argTypes != null && argTypes.length > i) {
357         PsiType argType = argTypes[i];
358         if (argType != null) {
359           final boolean converts1 = TypesUtil.isAssignable(type1, argType, manager, scope, false);
360           final boolean converts2 = TypesUtil.isAssignable(type2, argType, manager, scope, false);
361           if (converts1 != converts2) {
362             return converts2;
363           }
364         }
365       }
366
367       if (!typesAgree(manager, scope, type1, type2)) return false;
368     }
369
370     return true;
371   }
372
373   private boolean typesAgree(PsiManager manager, GlobalSearchScope scope, PsiType type1, PsiType type2) {
374     if (argumentsSupplied() && type1 instanceof PsiArrayType && !(type2 instanceof PsiArrayType)) {
375       type1 = ((PsiArrayType) type1).getComponentType();
376     }
377     return argumentsSupplied() ? //resolve, otherwise same_name_variants
378         TypesUtil.isAssignable(type1, type2, manager, scope, false) :
379         type1.equals(type2);
380   }
381
382   private boolean argumentsSupplied() {
383     return myArgumentTypes != null;
384   }
385
386
387   public boolean hasCandidates() {
388     return super.hasCandidates() || !myInapplicableCandidates.isEmpty();
389   }
390
391   public boolean hasApplicableCandidates() {
392     return super.hasCandidates();
393   }
394
395   @Nullable
396   public PsiType[] getArgumentTypes() {
397     return myArgumentTypes;
398   }
399
400   @Override
401   public void handleEvent(Event event, Object associated) {
402     super.handleEvent(event, associated);
403     if (JavaScopeProcessorEvent.CHANGE_LEVEL == event && super.hasCandidates()) {
404       myStopExecuting = true;
405     }
406   }
407 }