IDEA-51934: Groovy constructor calls should be disambiguated by parameter count
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / resolve / ResolveUtil.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;
18
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.psi.*;
22 import com.intellij.psi.scope.JavaScopeProcessorEvent;
23 import com.intellij.psi.scope.NameHint;
24 import com.intellij.psi.scope.PsiScopeProcessor;
25 import com.intellij.psi.search.GlobalSearchScope;
26 import com.intellij.psi.util.MethodSignature;
27 import com.intellij.psi.util.MethodSignatureUtil;
28 import com.intellij.psi.util.TypeConversionUtil;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.DynamicManager;
32 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
33 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
34 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
42 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
44 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
45 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassResolverProcessor;
46 import org.jetbrains.plugins.groovy.lang.resolve.processors.PropertyResolverProcessor;
47 import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
48
49 import java.util.*;
50
51 /**
52  * @author ven
53  */
54 @SuppressWarnings({"StringBufferReplaceableByString"})
55 public class ResolveUtil {
56   public static final PsiScopeProcessor.Event DECLARATION_SCOPE_PASSED = new PsiScopeProcessor.Event() {
57   };
58
59   private ResolveUtil() {
60   }
61
62   public static boolean treeWalkUp(PsiElement place, PsiScopeProcessor processor, boolean processNonCodeMethods) {
63     PsiElement lastParent = null;
64     PsiElement run = place;
65
66     final Project project = place.getProject();
67     PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
68
69     while (run != null) {
70       if (!run.processDeclarations(processor, ResolveState.initial(), lastParent, place)) return false;
71       if (processNonCodeMethods) {
72         if (run instanceof GrTypeDefinition) {
73           processNonCodeMethods(factory.createType(((GrTypeDefinition)run)), processor, project, place, false);
74         }
75         else if ((run instanceof GroovyFile) && ((GroovyFile)run).isScript()) {
76           processNonCodeMethods(factory.createType(((GroovyFile)run).getScriptClass()), processor, project, place, false);
77         }
78       }
79       lastParent = run;
80       run = run.getContext();
81       processor.handleEvent(JavaScopeProcessorEvent.CHANGE_LEVEL, null);
82     }
83
84     return true;
85   }
86
87   public static boolean processChildren(PsiElement element,
88                                         PsiScopeProcessor processor,
89                                         ResolveState substitutor,
90                                         PsiElement lastParent,
91                                         PsiElement place) {
92     PsiElement run = lastParent == null ? element.getLastChild() : lastParent.getPrevSibling();
93     while (run != null) {
94       if (!run.processDeclarations(processor, substitutor, null, place)) return false;
95       run = run.getPrevSibling();
96     }
97
98     return true;
99   }
100
101   public static boolean processElement(PsiScopeProcessor processor, PsiNamedElement namedElement) {
102     NameHint nameHint = processor.getHint(NameHint.KEY);
103     //todo [DIANA] look more carefully
104     String name = nameHint == null ? null : nameHint.getName(ResolveState.initial());
105     if (name == null || name.equals(namedElement.getName())) {
106       return processor.execute(namedElement, ResolveState.initial());
107     }
108
109     return true;
110   }
111
112   public static boolean processNonCodeMethods(PsiType type,
113                                               PsiScopeProcessor processor,
114                                               Project project,
115                                               PsiElement place,
116                                               boolean forCompletion) {
117     return processNonCodeMethods(type, processor, project, new HashSet<String>(), place, forCompletion);
118   }
119
120   private static boolean processNonCodeMethods(PsiType type,
121                                                PsiScopeProcessor processor,
122                                                Project project,
123                                                Set<String> visited,
124                                                PsiElement place,
125                                                boolean forCompletion) {
126     String qName = rawCanonicalText(type);
127
128     if (qName != null) {
129       if (visited.contains(qName)) return true;
130       visited.add(qName);
131       for (PsiMethod defaultMethod : GroovyPsiManager.getInstance(project).getDefaultMethods(qName)) {
132         if (!processElement(processor, defaultMethod)) return false;
133       }
134
135       for (PsiMethod method : DynamicManager.getInstance(project).getMethods(qName)) {
136         if (!processElement(processor, method)) return false;
137       }
138
139       for (PsiVariable var : DynamicManager.getInstance(project).getProperties(qName)) {
140         if (!processElement(processor, var)) return false;
141       }
142
143       for (NonCodeMembersProcessor membersProcessor : NonCodeMembersProcessor.EP_NAME.getExtensions()) {
144         if (!membersProcessor.processNonCodeMembers(type, processor, place, forCompletion)) return false;
145       }
146
147       if (type instanceof PsiArrayType && visited.size() == 1) {
148         //implicit super types
149         PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
150         PsiClassType t = factory.createTypeByFQClassName("java.lang.Object", GlobalSearchScope.allScope(project));
151         if (!processNonCodeMethods(t, processor, project, visited, place, forCompletion)) return false;
152         t = factory.createTypeByFQClassName("java.lang.Comparable", GlobalSearchScope.allScope(project));
153         if (!processNonCodeMethods(t, processor, project, visited, place, forCompletion)) return false;
154         t = factory.createTypeByFQClassName("java.io.Serializable", GlobalSearchScope.allScope(project));
155         if (!processNonCodeMethods(t, processor, project, visited, place, forCompletion)) return false;
156       }
157       for (PsiType superType : type.getSuperTypes()) {
158         if (!processNonCodeMethods(TypeConversionUtil.erasure(superType), processor, project, visited, place, forCompletion)) {
159           return false;
160         }
161       }
162     }
163
164     return true;
165   }
166
167   @Nullable
168   private static String rawCanonicalText(PsiType type) {
169     final String result = type.getCanonicalText();
170     if (result == null) return null;
171     final int i = result.indexOf('<');
172     if (i > 0) return result.substring(0, i);
173     return result;
174   }
175
176   @Nullable
177   public static PsiType getListTypeForSpreadOperator(GrReferenceExpression refExpr, PsiType componentType) {
178     PsiClass clazz = findListClass(refExpr.getManager(), refExpr.getResolveScope());
179     if (clazz != null) {
180       PsiTypeParameter[] typeParameters = clazz.getTypeParameters();
181       if (typeParameters.length == 1) {
182         PsiSubstitutor substitutor = PsiSubstitutor.EMPTY.put(typeParameters[0], componentType);
183         return JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory().createType(clazz, substitutor);
184       }
185     }
186
187     return null;
188   }
189
190   @Nullable
191   public static PsiClass findListClass(PsiManager manager, GlobalSearchScope resolveScope) {
192     return JavaPsiFacade.getInstance(manager.getProject()).findClass("java.util.List", resolveScope);
193   }
194
195   public static GroovyPsiElement resolveProperty(GroovyPsiElement place, String name) {
196     PropertyResolverProcessor processor = new PropertyResolverProcessor(name, place);
197     return resolveExistingElement(place, processor, GrVariable.class, GrReferenceExpression.class);
198   }
199
200   @Nullable
201   public static PsiClass resolveClass(GroovyPsiElement place, String name) {
202     ClassResolverProcessor processor = new ClassResolverProcessor(name, place);
203     return resolveExistingElement(place, processor, PsiClass.class);
204   }
205
206   @Nullable
207   public static <T> T resolveExistingElement(GroovyPsiElement place, ResolverProcessor processor, Class<? extends T>... classes) {
208     treeWalkUp(place, processor, true);
209     final GroovyResolveResult[] candidates = processor.getCandidates();
210     for (GroovyResolveResult candidate : candidates) {
211       final PsiElement element = candidate.getElement();
212       if (element == place) continue;
213       for (Class<? extends T> clazz : classes) {
214         if (clazz.isInstance(element)) return (T)element;
215       }
216     }
217
218     return null;
219   }
220
221   @NotNull
222   public static Pair<GrStatement, GrLabeledStatement> resolveLabelTargets(@Nullable String labelName,
223                                                                           @Nullable PsiElement element,
224                                                                           boolean isBreak) {
225     if (element == null) return new Pair<GrStatement, GrLabeledStatement>(null, null);
226
227     if (labelName == null) {
228       do {
229         element = element.getParent();
230         if (element == null || element instanceof GrClosableBlock || element instanceof GrMember || element instanceof GroovyFile) {
231           return new Pair<GrStatement, GrLabeledStatement>(null, null);
232         }
233       }
234       while (!(element instanceof GrLoopStatement) && !(isBreak && element instanceof GrSwitchStatement));
235       return new Pair<GrStatement, GrLabeledStatement>(((GrStatement)element), null);
236     }
237
238     GrStatement statement = null;
239     do {
240       PsiElement last = element;
241       element = element.getParent();
242       if (element == null || element instanceof GrMember || element instanceof GroovyFile) break;
243       if (element instanceof GrStatement && !(element instanceof GrClosableBlock)) {
244         statement = (GrStatement)element;
245       }
246       PsiElement sibling = element;
247       while (sibling != null) {
248         final GrLabeledStatement labelStatement = findLabelStatementIn(sibling, last, labelName);
249         if (labelStatement != null) {
250           return new Pair<GrStatement, GrLabeledStatement>(statement, labelStatement);
251         }
252         sibling = sibling.getPrevSibling();
253       }
254       if (element instanceof GrClosableBlock) break;
255     }
256     while (true);
257     return new Pair<GrStatement, GrLabeledStatement>(null, null);
258   }
259
260   private static boolean isApplicableLabelStatement(PsiElement element, String labelName) {
261     return ((element instanceof GrLabeledStatement && labelName.equals(((GrLabeledStatement)element).getLabelName())));
262   }
263
264   @Nullable
265   private static GrLabeledStatement findLabelStatementIn(PsiElement element, PsiElement lastChild, String labelName) {
266     if (isApplicableLabelStatement(element, labelName)) {
267       return (GrLabeledStatement)element;
268     }
269     for (PsiElement child = element.getFirstChild(); child != null && child != lastChild; child = child.getNextSibling()) {
270       final GrLabeledStatement statement = findLabelStatementIn(child, child, labelName);
271       if (statement != null) return statement;
272     }
273     return null;
274   }
275
276   @Nullable
277   public static GrLabeledStatement resolveLabeledStatement(@Nullable String labelName, @Nullable PsiElement element, boolean isBreak) {
278     return resolveLabelTargets(labelName, element, isBreak).second;
279   }
280
281   @Nullable
282   public static GrStatement resolveLabelTargetStatement(@Nullable String labelName, @Nullable PsiElement element, boolean isBreak) {
283     return resolveLabelTargets(labelName, element, isBreak).first;
284   }
285
286   public static boolean processCategoryMembers(PsiElement place, ResolverProcessor processor, PsiClassType thisType) {
287     PsiElement prev = null;
288     while (place != null) {
289       if (place instanceof GrMember) break;
290
291       if (place instanceof GrMethodCallExpression) {
292         final GrMethodCallExpression call = (GrMethodCallExpression)place;
293         final GrExpression invoked = call.getInvokedExpression();
294         if (invoked instanceof GrReferenceExpression && "use".equals(((GrReferenceExpression)invoked).getReferenceName())) {
295           final GrClosableBlock[] closures = call.getClosureArguments();
296           if (closures.length == 1 && closures[0].equals(prev)) {
297             if (useCategoryClass(call)) {
298               final GrArgumentList argList = call.getArgumentList();
299               if (argList != null) {
300                 final GrExpression[] args = argList.getExpressionArguments();
301                 if (args.length == 1 && args[0] instanceof GrReferenceExpression) {
302                   final PsiElement resolved = ((GrReferenceExpression)args[0]).resolve();
303                   if (resolved instanceof PsiClass) {
304                     try {
305                       processor.setCurrentFileResolveContext(call);
306                       if (!resolved.processDeclarations(processor, ResolveState.initial(), null, place)) return false;
307                     }
308                     finally {
309                       processor.setCurrentFileResolveContext(null);
310                     }
311                   }
312                 }
313               }
314             }
315           }
316         }
317       }
318
319       prev = place;
320       place = place.getContext();
321     }
322
323     return true;
324   }
325
326   private static boolean useCategoryClass(GrMethodCallExpression call) {
327
328     final PsiMethod resolved = call.resolveMethod();
329     if (resolved instanceof GrGdkMethod) {
330       final PsiElementFactory factory = JavaPsiFacade.getInstance(call.getProject()).getElementFactory();
331       final GlobalSearchScope scope = call.getResolveScope();
332       final PsiType[] parametersType =
333         {factory.createTypeByFQClassName("java.lang.Class", scope), factory.createTypeByFQClassName("groovy.lang.Closure", scope)};
334       final MethodSignature pattern =
335         MethodSignatureUtil.createMethodSignature("use", parametersType, PsiTypeParameter.EMPTY_ARRAY, PsiSubstitutor.EMPTY);
336       return resolved.getSignature(PsiSubstitutor.EMPTY).equals(pattern);
337     }
338
339     return false;
340   }
341
342   public static PsiElement[] mapToElements(GroovyResolveResult[] candidates) {
343     PsiElement[] elements = new PsiElement[candidates.length];
344     for (int i = 0; i < elements.length; i++) {
345       elements[i] = candidates[i].getElement();
346     }
347
348     return elements;
349   }
350
351   public static GroovyResolveResult[] filterSameSignatureCandidates(Collection<GroovyResolveResult> candidates, int argumentCount) {
352     GroovyResolveResult[] array = candidates.toArray(new GroovyResolveResult[candidates.size()]);
353     if (array.length == 1) return array;
354
355     List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
356     result.add(array[0]);
357
358     Outer:
359     for (int i = 1; i < array.length; i++) {
360       PsiElement currentElement = array[i].getElement();
361       if (currentElement instanceof PsiMethod) {
362         PsiMethod currentMethod = (PsiMethod)currentElement;
363         for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
364           final GroovyResolveResult otherResolveResult = iterator.next();
365           PsiElement element = otherResolveResult.getElement();
366           if (element instanceof PsiMethod) {
367             PsiMethod method = (PsiMethod)element;
368             if (dominated(currentMethod, array[i].getSubstitutor(), method, otherResolveResult.getSubstitutor(), argumentCount)) {
369               continue Outer;
370             }
371             else if (dominated(method, otherResolveResult.getSubstitutor(), currentMethod, array[i].getSubstitutor(), argumentCount)) {
372               iterator.remove();
373             }
374           }
375         }
376       }
377
378       result.add(array[i]);
379     }
380
381     return result.toArray(new GroovyResolveResult[result.size()]);
382   }
383
384   public static boolean dominated(PsiMethod method1,
385                                   PsiSubstitutor substitutor1,
386                                   PsiMethod method2,
387                                   PsiSubstitutor substitutor2,
388                                   int argumentCount) {  //method1 has more general parameter types then method2
389     if (!method1.getName().equals(method2.getName())) return false;
390
391     PsiParameter[] params1 = method1.getParameterList().getParameters();
392     PsiParameter[] params2 = method2.getParameterList().getParameters();
393     if (argumentCount != params1.length && argumentCount == params2.length) return true;
394
395     if (params1.length != params2.length) return false;
396
397     for (int i = 0; i < params2.length; i++) {
398       PsiType type1 = substitutor1.substitute(params1[i].getType());
399       PsiType type2 = substitutor2.substitute(params2[i].getType());
400       if (!type1.equals(type2)) return false;
401     }
402
403     return true;
404   }
405 }