Groovy type assignability
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / psi / impl / statements / expressions / CompleteReferenceExpression.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 package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions;
17
18 import com.intellij.codeInsight.PsiEquivalenceUtil;
19 import com.intellij.codeInsight.lookup.LookupElement;
20 import com.intellij.codeInsight.lookup.LookupElementBuilder;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Condition;
23 import com.intellij.psi.*;
24 import com.intellij.psi.search.GlobalSearchScope;
25 import com.intellij.psi.util.InheritanceUtil;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.TypeConversionUtil;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.Consumer;
30 import com.intellij.util.containers.ContainerUtil;
31 import gnu.trove.THashSet;
32 import org.jetbrains.plugins.groovy.GroovyIcons;
33 import org.jetbrains.plugins.groovy.lang.completion.GroovyCompletionUtil;
34 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
35 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
36 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
37 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
38 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConstructorCall;
42 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
44 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression;
45 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
46 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod;
47 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
49 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
50 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
51 import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
52 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
53 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
54 import org.jetbrains.plugins.groovy.lang.resolve.processors.CompletionProcessor;
55 import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
56
57 import java.util.ArrayList;
58 import java.util.LinkedHashSet;
59 import java.util.List;
60 import java.util.Set;
61
62 /**
63  * @author ven
64  */
65 public class CompleteReferenceExpression {
66   private CompleteReferenceExpression() {
67   }
68
69   public static void processVariants(Consumer<Object> consumer, GrReferenceExpressionImpl refExpr) {
70     PsiElement refParent = refExpr.getParent();
71     processArgsVariants(consumer, refParent);
72
73     final GrExpression qualifier = refExpr.getQualifierExpression();
74     Object[] propertyVariants = getVariantsImpl(refExpr, CompletionProcessor.createPropertyCompletionProcessor(refExpr));
75     PsiType type = null;
76     if (qualifier == null) {
77       if (refParent instanceof GrArgumentList) {
78         final PsiElement argList = refParent.getParent();
79         if (argList instanceof GrExpression) {
80           GrExpression call = (GrExpression)argList; //add named argument label variants
81           type = call.getType();
82         }
83       }
84     }
85     else {
86       type = qualifier.getType();
87     }
88
89     if (type instanceof PsiClassType) {
90       PsiClass clazz = ((PsiClassType)type).resolve();
91       if (clazz != null) {
92         Set<String> accessedPropertyNames = processPropertyVariants(consumer, refExpr, clazz);
93         for (Object variant : propertyVariants) {
94           if (variant instanceof PsiField && accessedPropertyNames.contains(((PsiField)variant).getName())) {
95             continue;
96           }
97           consumer.consume(variant);
98         }
99       }
100     }
101     else {
102       for (Object variant : propertyVariants) {
103         consumer.consume(variant);
104       }
105     }
106
107     if (refExpr.getKind() == GrReferenceExpressionImpl.Kind.TYPE_OR_PROPERTY) {
108       ResolverProcessor classVariantsCollector = CompletionProcessor.createClassCompletionProcessor(refExpr);
109       final Object[] classVariants = getVariantsImpl(refExpr, classVariantsCollector);
110       for (Object variant : classVariants) {
111         consumer.consume(variant);
112       }
113     }
114   }
115
116   private static void processArgsVariants(Consumer<Object> consumer, PsiElement refParent) {
117     if (refParent instanceof GrArgumentList) {
118       PsiElement refPParent = refParent.getParent();
119       if (refPParent instanceof GrCall) {
120         GroovyResolveResult[] results = new GroovyResolveResult[0];
121         //costructor call
122         if (refPParent instanceof GrConstructorCall) {
123           GrConstructorCall constructorCall = (GrConstructorCall)refPParent;
124           results = ArrayUtil.mergeArrays(results, constructorCall.multiResolveConstructor(), GroovyResolveResult.class);
125         }
126         else
127           //call expression (method or new expression)
128           if (refPParent instanceof GrCallExpression) {
129             GrCallExpression constructorCall = (GrCallExpression)refPParent;
130             results = ArrayUtil.mergeArrays(results, constructorCall.getMethodVariants(), GroovyResolveResult.class);
131           }
132           else if (refPParent instanceof GrApplicationStatementImpl) {
133             final GrExpression element = ((GrApplicationStatementImpl)refPParent).getFunExpression();
134             if (element instanceof GrReferenceElement) {
135               results = ArrayUtil.mergeArrays(results, ((GrReferenceElement)element).multiResolve(true), GroovyResolveResult.class);
136             }
137           }
138
139
140         for (GroovyResolveResult result : results) {
141           PsiElement element = result.getElement();
142           if (element instanceof GrMethod) {
143             Set<String>[] parametersArray = ((GrMethod)element).getNamedParametersArray();
144             for (Set<String> namedParameters : parametersArray) {
145               for (String parameter : namedParameters) {
146                 consumer.consume(parameter);
147               }
148             }
149           }
150         }
151       }
152     }
153   }
154
155   private static Set<String> processPropertyVariants(Consumer<Object> consumer, GrReferenceExpression refExpr, PsiClass clazz) {
156     Set<String> accessedPropertyNames=new THashSet<String>();
157     final PsiClass eventListener =
158       JavaPsiFacade.getInstance(refExpr.getProject()).findClass("java.util.EventListener", refExpr.getResolveScope());
159     for (PsiMethod method : clazz.getAllMethods()) {
160       if (PsiUtil.isStaticsOK(method, refExpr)) {
161         if (GroovyPropertyUtils.isSimplePropertyAccessor(method)) {
162           String prop = GroovyPropertyUtils.getPropertyName(method);
163           accessedPropertyNames.add(prop);
164           assert prop != null;
165           consumer.consume(LookupElementBuilder.create(prop).setIcon(GroovyIcons.PROPERTY));
166         }
167         else if (eventListener != null) {
168           consumeListenerProperties(consumer, method, eventListener);
169         }
170       }
171     }
172     return accessedPropertyNames;
173   }
174
175   private static void consumeListenerProperties(Consumer<Object> consumer, PsiMethod method, PsiClass eventListenerClass) {
176     if (method.getName().startsWith("add") && method.getParameterList().getParametersCount() == 1) {
177       final PsiParameter parameter = method.getParameterList().getParameters()[0];
178       final PsiType type = parameter.getType();
179       if (type instanceof PsiClassType) {
180         final PsiClassType classType = (PsiClassType)type;
181         final PsiClass listenerClass = classType.resolve();
182         if (listenerClass != null) {
183           final PsiMethod[] listenerMethods = listenerClass.getMethods();
184           if (InheritanceUtil.isInheritorOrSelf(listenerClass, eventListenerClass, true)) {
185             for (PsiMethod listenerMethod : listenerMethods) {
186               consumer.consume(LookupElementBuilder.create(listenerMethod.getName()).setIcon(GroovyIcons.PROPERTY));
187             }
188           }
189         }
190       }
191     }
192   }
193
194
195   private static Object[] getVariantsImpl(GrReferenceExpression refExpr, ResolverProcessor processor) {
196     GrExpression qualifier = refExpr.getQualifierExpression();
197     String[] sameQualifier = getVariantsWithSameQualifier(qualifier, refExpr);
198     if (qualifier == null) {
199       ResolveUtil.treeWalkUp(refExpr, processor, true);
200       qualifier = PsiImplUtil.getRuntimeQualifier(refExpr);
201       if (qualifier != null) {
202         getVariantsFromQualifier(refExpr, processor, qualifier);
203       }
204     }
205     else {
206       if (refExpr.getDotTokenType() != GroovyTokenTypes.mSPREAD_DOT) {
207         getVariantsFromQualifier(refExpr, processor, qualifier);
208       }
209       else {
210         getVariantsFromQualifierForSpreadOperator(refExpr, processor, qualifier);
211       }
212     }
213
214     GroovyResolveResult[] candidates = processor.getCandidates();
215     if (candidates.length == 0 && sameQualifier.length == 0) return PsiNamedElement.EMPTY_ARRAY;
216     candidates = filterStaticsOK(candidates);
217     PsiElement[] elements = ResolveUtil.mapToElements(candidates);
218     if (qualifier == null) {
219       List<GroovyResolveResult> nonPackages = ContainerUtil.findAll(candidates, new Condition<GroovyResolveResult>() {
220         public boolean value(final GroovyResolveResult result) {
221           return !(result.getElement() instanceof PsiPackage);
222         }
223       });
224       candidates = nonPackages.toArray(new GroovyResolveResult[nonPackages.size()]);
225     }
226     LookupElement[] propertyLookupElements = addPretendedProperties(elements);
227     Object[] variants = GroovyCompletionUtil.getCompletionVariants(candidates);
228     variants = ArrayUtil.mergeArrays(variants, propertyLookupElements, Object.class);
229     return ArrayUtil.mergeArrays(variants, sameQualifier, Object.class);
230   }
231
232   private static GroovyResolveResult[] filterStaticsOK(GroovyResolveResult[] candidates) {
233     List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>(candidates.length);
234     for (GroovyResolveResult resolveResult : candidates) {
235       if (resolveResult.isStaticsOK()) result.add(resolveResult);
236     }
237     return result.toArray(new GroovyResolveResult[result.size()]);
238   }
239
240   private static void getVariantsFromQualifierForSpreadOperator(GrReferenceExpression refExpr,
241                                                                 ResolverProcessor processor,
242                                                                 GrExpression qualifier) {
243     PsiType qualifierType = qualifier.getType();
244     if (qualifierType instanceof PsiClassType) {
245       PsiClassType.ClassResolveResult result = ((PsiClassType)qualifierType).resolveGenerics();
246       PsiClass clazz = result.getElement();
247       if (clazz != null) {
248         PsiClass listClass = JavaPsiFacade.getInstance(refExpr.getProject()).findClass("java.util.List", refExpr.getResolveScope());
249         if (listClass != null && listClass.getTypeParameters().length == 1) {
250           PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(listClass, clazz, result.getSubstitutor());
251           if (substitutor != null) {
252             PsiType componentType = substitutor.substitute(listClass.getTypeParameters()[0]);
253             if (componentType != null) {
254               getVariantsFromQualifierType(refExpr, processor, componentType, refExpr.getProject());
255             }
256           }
257         }
258       }
259     }
260     else if (qualifierType instanceof PsiArrayType) {
261       getVariantsFromQualifierType(refExpr, processor, ((PsiArrayType)qualifierType).getComponentType(), refExpr.getProject());
262     }
263   }
264
265   private static LookupElement[] addPretendedProperties(PsiElement[] elements) {
266     List<LookupElement> result = new ArrayList<LookupElement>();
267
268     for (PsiElement element : elements) {
269       if (element instanceof PsiMethod && !(element instanceof GrAccessorMethod)) {
270         PsiMethod method = (PsiMethod)element;
271         if (GroovyPropertyUtils.isSimplePropertyAccessor(method)) {
272           String propName = GroovyPropertyUtils.getPropertyName(method);
273           if (!PsiUtil.isValidReferenceName(propName)) {
274             propName = "'" + propName + "'";
275           }
276           assert propName != null;
277           result.add(LookupElementBuilder.create(propName).setIcon(GroovyIcons.PROPERTY));
278         }
279       }
280     }
281
282     return result.toArray(new LookupElement[result.size()]);
283   }
284
285   private static void getVariantsFromQualifier(GrReferenceExpression refExpr, ResolverProcessor processor, GrExpression qualifier) {
286     Project project = qualifier.getProject();
287     PsiType qualifierType = qualifier.getType();
288     if (qualifierType == null) {
289       if (qualifier instanceof GrReferenceExpression) {
290         PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
291         if (resolved instanceof PsiPackage) {
292           resolved.processDeclarations(processor, ResolveState.initial(), null, refExpr);
293           return;
294         }
295       }
296       final PsiClassType type = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory()
297         .createTypeByFQClassName(GrTypeDefinition.DEFAULT_BASE_CLASS_NAME, refExpr.getResolveScope());
298       getVariantsFromQualifierType(refExpr, processor, type, project);
299     }
300     else {
301       if (qualifierType instanceof PsiIntersectionType) {
302         for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
303           getVariantsFromQualifierType(refExpr, processor, conjunct, project);
304         }
305       }
306       else {
307         getVariantsFromQualifierType(refExpr, processor, qualifierType, project);
308         if (qualifier instanceof GrReferenceExpression) {
309           PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
310           if (resolved instanceof PsiClass) { ////omitted .class
311             GlobalSearchScope scope = refExpr.getResolveScope();
312             PsiClass javaLangClass = PsiUtil.getJavaLangClass(resolved, scope);
313             if (javaLangClass != null) {
314               PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
315               PsiTypeParameter[] typeParameters = javaLangClass.getTypeParameters();
316               if (typeParameters.length == 1) {
317                 substitutor = substitutor.put(typeParameters[0], qualifierType);
318               }
319               javaLangClass.processDeclarations(processor, ResolveState.initial(), null, refExpr);
320               PsiType javaLangClassType =
321                 JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory().createType(javaLangClass, substitutor);
322               ResolveUtil.processNonCodeMethods(javaLangClassType, processor, refExpr.getProject(), refExpr, true);
323             }
324           }
325         }
326       }
327     }
328   }
329
330   private static String[] getVariantsWithSameQualifier(GrExpression qualifier, GrReferenceExpression refExpr) {
331     if (qualifier != null && qualifier.getType() != null) return ArrayUtil.EMPTY_STRING_ARRAY;
332
333     final PsiElement scope = PsiTreeUtil.getParentOfType(refExpr, GrMember.class, GroovyFileBase.class);
334     Set<String> result = new LinkedHashSet<String>();
335     addVariantsWithSameQualifier(scope, refExpr, qualifier, result);
336     return ArrayUtil.toStringArray(result);
337   }
338
339   private static void addVariantsWithSameQualifier(PsiElement element,
340                                                    GrReferenceExpression patternExpression,
341                                                    GrExpression patternQualifier,
342                                                    Set<String> result) {
343     if (element instanceof GrReferenceExpression && element != patternExpression && !PsiUtil.isLValue((GroovyPsiElement)element)) {
344       final GrReferenceExpression refExpr = (GrReferenceExpression)element;
345       final String refName = refExpr.getReferenceName();
346       if (refName != null && !result.contains(refName)) {
347         final GrExpression hisQualifier = refExpr.getQualifierExpression();
348         if (hisQualifier != null && patternQualifier != null) {
349           if (PsiEquivalenceUtil.areElementsEquivalent(hisQualifier, patternQualifier)) {
350             if (refExpr.resolve() == null) {
351               result.add(refName);
352             }
353           }
354         }
355         else if (hisQualifier == null && patternQualifier == null) {
356           if (refExpr.resolve() == null) {
357             result.add(refName);
358           }
359         }
360       }
361     }
362
363     for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
364       addVariantsWithSameQualifier(child, patternExpression, patternQualifier, result);
365     }
366   }
367
368   private static void getVariantsFromQualifierType(GrReferenceExpression refExpr,
369                                                    ResolverProcessor processor,
370                                                    PsiType qualifierType,
371                                                    Project project) {
372     if (qualifierType instanceof PsiClassType) {
373       PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve();
374       if (qualifierClass != null) {
375         qualifierClass.processDeclarations(processor, ResolveState.initial(), null, refExpr);
376       }
377       if (!ResolveUtil.processCategoryMembers(refExpr, processor, (PsiClassType)qualifierType)) return;
378     }
379     else if (qualifierType instanceof PsiArrayType) {
380       final GrTypeDefinition arrayClass = GroovyPsiManager.getInstance(project).getArrayClass();
381       if (!arrayClass.processDeclarations(processor, ResolveState.initial(), null, refExpr)) return;
382     }
383     else if (qualifierType instanceof PsiIntersectionType) {
384       for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
385         getVariantsFromQualifierType(refExpr, processor, conjunct, project);
386       }
387       return;
388     }
389
390     ResolveUtil.processNonCodeMethods(qualifierType, processor, project, refExpr, true);
391   }
392 }