IDEA-51934: Groovy constructor calls should be disambiguated by parameter count
[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 org.jetbrains.annotations.Nullable;
26 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
27 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
28 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
29 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
30 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
31 import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
32 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
33 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
34 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
36 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
37 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
38 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
39 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
40
41 import java.util.*;
42
43 /**
44  * @author ven
45  */
46 public class MethodResolverProcessor extends ResolverProcessor {
47   private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.lang.resolve.processors.MethodResolverProcessor");
48   private final PsiType myThisType;
49   @Nullable
50   private PsiType[] myArgumentTypes;
51   private final PsiType[] myTypeArguments;
52
53   private final Set<GroovyResolveResult> myInapplicableCandidates = new LinkedHashSet<GroovyResolveResult>();
54   private final boolean myIsConstructor;
55
56   private boolean myStopExecuting = false;
57
58   public MethodResolverProcessor(String name, GroovyPsiElement place, boolean isConstructor, PsiType thisType, @Nullable PsiType[] argumentTypes, PsiType[] typeArguments) {
59     super(name, EnumSet.of(ResolveKind.METHOD, ResolveKind.PROPERTY), place, PsiType.EMPTY_ARRAY);
60     myIsConstructor = isConstructor;
61     myThisType = thisType;
62     myArgumentTypes = argumentTypes;
63     myTypeArguments = typeArguments;
64   }
65
66   public boolean execute(PsiElement element, ResolveState state) {
67     if (myStopExecuting) {
68       return false;
69     }
70     PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
71     if (element instanceof PsiMethod) {
72       PsiMethod method = (PsiMethod) element;
73       if (method.isConstructor() != myIsConstructor) return true;
74       if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
75       substitutor = obtainSubstitutor(substitutor, method);
76       boolean isAccessible = isAccessible(method);
77       boolean isStaticsOK = isStaticsOK(method);
78       if (PsiUtil.isApplicable(myArgumentTypes, method, substitutor, myCurrentFileResolveContext instanceof GrMethodCallExpression)) {
79         myCandidates.add(new GroovyResolveResultImpl(method, myCurrentFileResolveContext, substitutor, isAccessible, isStaticsOK));
80       } else {
81         myInapplicableCandidates.add(new GroovyResolveResultImpl(method, myCurrentFileResolveContext, substitutor, isAccessible, isStaticsOK));
82       }
83
84       return true;
85     } else if (element instanceof PsiVariable) {
86       if (element instanceof GrField && ((GrField) element).isProperty() ||
87           isClosure((PsiVariable) element)) {
88         return super.execute(element, state);
89       } else {
90         myInapplicableCandidates.add(new GroovyResolveResultImpl(element, myCurrentFileResolveContext, substitutor, isAccessible((PsiVariable)element), isStaticsOK((PsiVariable)element)));
91       }
92     }
93
94
95     return true;
96   }
97
98   private PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method) {
99     final PsiTypeParameter[] typeParameters = method.getTypeParameters();
100     if (myTypeArguments.length == typeParameters.length) {
101       for (int i = 0; i < typeParameters.length; i++) {
102         PsiTypeParameter typeParameter = typeParameters[i];
103         final PsiType typeArgument = myTypeArguments[i];
104         substitutor = substitutor.put(typeParameter, typeArgument);
105       }
106       return substitutor;
107     }
108
109     if (argumentsSupplied() && method.hasTypeParameters()) {
110       PsiType[] argTypes = myArgumentTypes;
111       if (method instanceof GrGdkMethod) {
112         assert argTypes != null;
113         //type inference should be performed from static method
114         PsiType[] newArgTypes = new PsiType[argTypes.length + 1];
115         newArgTypes[0] = myThisType;
116         System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length);
117         argTypes = newArgTypes;
118
119         method = ((GrGdkMethod) method).getStaticMethod();
120         LOG.assertTrue(method.isValid());
121       }
122       return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes);
123     }
124
125     return substitutor;
126   }
127
128   private static boolean isClosure(PsiVariable variable) {
129     if (variable instanceof GrVariable) {
130       final PsiType type = ((GrVariable) variable).getTypeGroovy();
131       return type != null && type.equalsToText(GrClosableBlock.GROOVY_LANG_CLOSURE);
132     }
133     return variable.getType().equalsToText(GrClosableBlock.GROOVY_LANG_CLOSURE);
134   }
135
136   private PsiSubstitutor inferMethodTypeParameters(PsiMethod method, PsiSubstitutor partialSubstitutor, final PsiTypeParameter[] typeParameters, final PsiType[] argTypes) {
137     if (typeParameters.length == 0) return partialSubstitutor;
138
139     if (argumentsSupplied()) {
140       final PsiParameter[] parameters = method.getParameterList().getParameters();
141       final int max = Math.max(parameters.length, argTypes.length);
142       PsiType[] parameterTypes = new PsiType[max];
143       PsiType[] argumentTypes = new PsiType[max];
144       for (int i = 0; i < parameterTypes.length; i++) {
145         if (i < parameters.length) {
146           final PsiType type = parameters[i].getType();
147           if (argTypes.length == parameters.length &&
148               type instanceof PsiEllipsisType &&
149               !(argTypes[argTypes.length - 1] instanceof PsiArrayType)) {
150             parameterTypes[i] = ((PsiEllipsisType) type).getComponentType();
151           } else {
152             parameterTypes[i] = type;
153           }
154         } else {
155           if (parameters.length > 0) {
156             final PsiType lastParameterType = parameters[parameters.length - 1].getType();
157             if (argTypes.length > parameters.length && lastParameterType instanceof PsiEllipsisType) {
158               parameterTypes[i] = ((PsiEllipsisType) lastParameterType).getComponentType();
159             } else {
160               parameterTypes[i] = lastParameterType;
161             }
162           } else {
163             parameterTypes[i] = PsiType.NULL;
164           }
165         }
166         argumentTypes[i] = i < argTypes.length ? argTypes[i] : PsiType.NULL;
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, method.getReturnType(), substitutor, helper);
174         }
175       }
176
177       return partialSubstitutor.putAll(substitutor);
178     }
179
180     return partialSubstitutor;
181   }
182
183   private PsiSubstitutor inferFromContext(PsiTypeParameter typeParameter, PsiType lType, PsiSubstitutor substitutor, PsiResolveHelper helper) {
184     if (myPlace != null) {
185       final PsiType inferred = helper.getSubstitutionForTypeParameter(typeParameter, lType, getContextType(), false, LanguageLevel.HIGHEST);
186       if (inferred != PsiType.NULL) {
187         return substitutor.put(typeParameter, inferred);
188       }
189     }
190     return substitutor;
191   }
192
193   @Nullable
194   private PsiType getContextType() {
195     final PsiElement parent = myPlace.getParent().getParent();
196     PsiType rType = null;
197     if (parent instanceof GrReturnStatement) {
198       final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class);
199       if (method != null) rType = method.getDeclaredReturnType();
200     }
201     else if (parent instanceof GrAssignmentExpression && myPlace.equals(((GrAssignmentExpression)parent).getRValue())) {
202       rType = ((GrAssignmentExpression)parent).getLValue().getType();
203     }
204     else if (parent instanceof GrVariable) {
205       rType = ((GrVariable)parent).getDeclaredType();
206     }
207     return rType;
208   }
209
210   public GroovyResolveResult[] getCandidates() {
211     if (!myCandidates.isEmpty()) {
212       return filterCandidates();
213     }
214     if (!myInapplicableCandidates.isEmpty()) {
215       return ResolveUtil.filterSameSignatureCandidates(myInapplicableCandidates, myArgumentTypes != null ? myArgumentTypes.length : -1);
216     }
217     return GroovyResolveResult.EMPTY_ARRAY;
218   }
219
220   private GroovyResolveResult[] filterCandidates() {
221     GroovyResolveResult[] array = myCandidates.toArray(new GroovyResolveResult[myCandidates.size()]);
222     if (array.length == 1) return array;
223
224     List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
225     result.add(array[0]);
226
227     PsiManager manager = myPlace.getManager();
228     GlobalSearchScope scope = myPlace.getResolveScope();
229
230     boolean methodsPresent = array[0].getElement() instanceof PsiMethod;
231     boolean propertiesPresent = !methodsPresent;
232     Outer:
233     for (int i = 1; i < array.length; i++) {
234       PsiElement currentElement = array[i].getElement();
235       if (currentElement instanceof PsiMethod) {
236         methodsPresent = true;
237         PsiMethod currentMethod = (PsiMethod) currentElement;
238         for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
239           final GroovyResolveResult otherResolveResult = iterator.next();
240           PsiElement element = otherResolveResult.getElement();
241           if (element instanceof PsiMethod) {
242             PsiMethod method = (PsiMethod) element;
243             if (dominated(currentMethod, array[i].getSubstitutor(), method, otherResolveResult.getSubstitutor(), manager, scope)) {
244               continue Outer;
245             } else
246             if (dominated(method, otherResolveResult.getSubstitutor(), currentMethod, array[i].getSubstitutor(), manager, scope)) {
247               iterator.remove();
248             }
249           }
250         }
251       } else {
252         propertiesPresent = true;
253       }
254
255       result.add(array[i]);
256     }
257
258     if (methodsPresent && propertiesPresent) {
259       for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
260         GroovyResolveResult resolveResult = iterator.next();
261         if (!(resolveResult.getElement() instanceof PsiMethod)) iterator.remove();
262       }
263     }
264
265     return result.toArray(new GroovyResolveResult[result.size()]);
266   }
267
268   private boolean dominated(PsiMethod method1, PsiSubstitutor substitutor1, PsiMethod method2, PsiSubstitutor substitutor2, PsiManager manager, GlobalSearchScope scope) {  //method1 has more general parameter types thn method2
269     if (!method1.getName().equals(method2.getName())) return false;
270
271     //hack for default gdk methods
272     if (method1 instanceof GrGdkMethod && method2 instanceof GrGdkMethod) {
273       method1 = ((GrGdkMethod)method1).getStaticMethod();
274       method2 = ((GrGdkMethod)method2).getStaticMethod();
275     }
276     PsiParameter[] params1 = method1.getParameterList().getParameters();
277     PsiParameter[] params2 = method2.getParameterList().getParameters();
278     if (myArgumentTypes == null && params1.length != params2.length) return false;
279
280     if (params1.length < params2.length) {
281       if (params1.length == 0) return false;
282       final PsiType lastType = params1[params1.length - 1].getType(); //varargs applicability
283       return lastType instanceof PsiArrayType;
284     }
285
286     for (int i = 0; i < params2.length; i++) {
287       PsiType type1 = substitutor1.substitute(params1[i].getType());
288       PsiType type2 = substitutor2.substitute(params2[i].getType());
289       if (!typesAgree(manager, scope, type1, type2)) return false;
290     }
291
292     return true;
293   }
294
295   private boolean typesAgree(PsiManager manager, GlobalSearchScope scope, PsiType type1, PsiType type2) {
296     if (argumentsSupplied() && type1 instanceof PsiArrayType && !(type2 instanceof PsiArrayType)) {
297       type1 = ((PsiArrayType) type1).getComponentType();
298     }
299     return argumentsSupplied() ? //resolve, otherwise same_name_variants
300         TypesUtil.isAssignable(type1, type2, manager, scope) :
301         type1.equals(type2);
302   }
303
304   private boolean argumentsSupplied() {
305     return myArgumentTypes != null;
306   }
307
308
309   public boolean hasCandidates() {
310     return super.hasCandidates() || !myInapplicableCandidates.isEmpty();
311   }
312
313   @Nullable
314   public PsiType[] getArgumentTypes() {
315     return myArgumentTypes;
316   }
317
318   public void setArgumentTypes(@Nullable PsiType[] argumentTypes) {
319     myArgumentTypes = argumentTypes;
320   }
321
322   @Override
323   public void handleEvent(Event event, Object associated) {
324     super.handleEvent(event, associated);
325     if (JavaScopeProcessorEvent.CHANGE_LEVEL == event && myCandidates.size() > 0) {
326       myStopExecuting = true;
327     }
328   }
329 }